socket网络编程
2021/7/10 20:38:09
本文主要是介绍socket网络编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
(一) socket编程基础知识
学习socket编程前,我们先来提升一下认知:
(1) 地址
地址由IP地址、端口号构成
- IP地址:用于设备标识
- 端口号:用于标识网络服务(ftp、http、socket) (5000到10000之间选择)
IP地址转换API(IP地址是字符串要转换成网络能识别格式)
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp); //字符串转网络字节序 注意第二个参数是结构体 char *inet_ntoa(struct in_addr in); //网络字节序转字符串
字节序转换API (端口号: 主机字节序转换成网络字节序)
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); //将无符号整数hostlong从主机字节顺序转换为网络字节顺序 uint16_t htons(uint16_t hostshort); //将无符号短整数hostshort从主机字节顺序转换为网络字节顺序 uint32_t ntohl(uint32_t netlong); //将无符号整数netlong从网络字节顺序转换为主机字节顺序 uint16_t ntohs(uint16_t netshort); //将无符号短整数netshort从网络字节顺序转换为主机字节顺序
h --> host(主机) n --> net(网络) s --> short(小端) l --> long(大端)
(2) 传输协议
- TCP:面向连接 (先建立连接再发送数据)
- 可靠性强。用于传输精度高数据量小的交互通信,一对一交互,无差错,不丢失,不重复,且按序到达
- 全双工可靠信道
- UDP:面向报文 (无需连接直接发送数据)
- 传输能力强。用于传输精度要求不高数据量大的交互通信,会有数据丢失,支持一对一,一对多,多对一,多对多交互
- 不可靠信道
(二) socket编程步骤
步骤分为客户端和服务器端,详细步骤如下图示:
(三) socket编程API
(1) socket()函数
作用:获取套接字
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol)
- domain
指定所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)- AF_INET IPv4 因特网
- AF_INET6 IPv6 因特网
- AF_UNIX Uinix域
- AF_ROUTE 路由套接字
- AF_KEY 密钥套接字
- AF_UNSPEC 未指定
- type
指定socket类型:- SOCK_STREAM 指定TCP协议
字节流套接字提供可靠的、面向连接的通信流;他使用TCP协议,从而保证了数据传输正确性和顺序性 - SOCK_DGRAM 指定UDP协议
数据报文套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,使用UDP协议 - SOCK_RAW 指定底层协议
允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用不便,主要用于一些协议的开发- 项目
- SOCK_STREAM 指定TCP协议
- protocol
通常赋值为0- 0 选择type类型的默认协议TCP协议
- IPPROTO_TCP TCP传输协议
- IPPPOTO_UDP UDP传输协议
- IPPROTO_SCTP SCTP传输协议
- IPPROTO_TIPC TIPC传输协议
(2)bind()函数
功能:绑定IP号和端口号到socketfd
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
- sockfd
是一个socket描述符 - addr
是一个指向包含有本机IP地址及端口号等信息的struct sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同 - addrlen
结构体add的大小 - 返回值
成功返回0 ,失败返回-1,errno被设置
struct sockaddr ----IPv4
struct sockaddr { sa_family_t sa_family; //协议族 char sa_data[14]; //IP地址+端口号 };
常用struct sockaddr_in类型强转替换struct sockaddr
struct sockaddr_in { __kernel_sa_family_t sin_family; //协议族 __be16 sin_port; //端口号 struct in_addr sin_addr; //IP地址结构体 /* 填充 只为了跟sockaddr结构体对齐才能相互转换 */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { __be32 s_addr; };
命令行查找struct sockaddr_in
- 进入头文件文件夹
cd /usr/include/
- 当前文件夹下显示行数不区分大小写递归查找
grep "struct sockaddr_in {" * -nir
- 进入头文件查看
vi linux/in.h +184
(3) listen()函数
功能:设置最大连接数,一直监听是否有其他的客户进程请求连接,响应连接TCP三次握手
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
- sockfd
sockfd进行监听的套接字 - backlog
指定在请求队列中允许的最大请求数 - 返回值
成功返回0 ,失败返回-1,errno被设置
(4) accept()函数
功能:与客户端建立连接,保存客户端套接字对应的“地方”(包括客户端IP和端口信息等),如果已完成请求的队列为空将阻塞到下一个请求到来
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd
监听的套接字 - addr (可选择不使用填NULL)
用于保存已连接的客户端的地址 - addrled (可选择不使用填NULL)
客户端地址长度 - 返回值
返回一个已连接的新的套接字,交互完成后该套接字就会被关闭,第一个参数为监听套接字且服务器只创建一个
失败返回-1,errno被设置、
(5) 数据收发函数
收发函数有很多套可以使用:
- read()、write() 已连接状态使用
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
- recv()、send() 已连接状态使用
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags);
使用与read、write类似, flags控制选项常设置为0
- readv()、wirtev() 已连接状态使用
#include <sys/uio.h> ssize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt); struct iovec { void *iov_base; /*指向一个缓冲区,这个缓冲区是存放readv()所接收的数据或 //writev()将要发送的数据*/ size_t iov_len; /*接收的最大长度以及实际写入的长度*/ };
iovcnt 为iov大小
- recvmsg()、sendmsg() 未连接状态可使用
#include <sys/types.h> #include <sys/socket.h> ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ };
- recvfrom()、sendto() 未连接状态可使用
#include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
1、2、3套多用于TCP
4、5套多用于UDP
(6) connect()函数
功能:向服务端发出连接请求并进行连接
//#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd
与服务端连接的套接字 - addr
服务端IP地址与端口号地址结构体指针 - addrlen
常设置为 sizeof(struct sockaddr) - 返回值
成功返回0 ,失败返回-1,errno被设置
(四) demo
socket编程实现多方收发信息
服务端
#include <stdio.h> #include <sys/socket.h> #include <errno.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <string.h> int main() { int s_fd = 0; int ret = 0; int c_fd = 0; int n_read; int n_write; char w_buf[128]; char r_buf[128]; struct sockaddr_in addr; struct in_addr sin_addr; s_fd = socket(AF_INET, SOCK_STREAM, 0); if (s_fd == -1){ perror("socket "); exit(-1); } addr.sin_family = AF_INET; addr.sin_port = htons(8888); inet_aton("192.168.1.8", &addr.sin_addr); ret = bind(s_fd, (const struct sockaddr*)&addr, sizeof(addr)); if (ret == -1){ perror("bind "); exit(-1); } listen(s_fd, 10); int addrlen = 0; struct sockaddr_in s_addr; addrlen = sizeof(s_addr); while(1){ c_fd = accept(s_fd, (struct sockaddr*)&s_addr, &addrlen); if (c_fd == -1){ perror("accept "); exit(-1); } printf("get connect IP: %s\n", inet_ntoa(s_addr.sin_addr)); if (fork() == 0){ write(c_fd, "welcome to server!", strlen("welcome to server!")); if (fork() == 0){ while(1){ memset(r_buf, 0, 128); n_read = read(c_fd, r_buf, sizeof(r_buf)); if (n_read == -1){ perror("read "); } else if (n_read == 0){ printf("IP :%s clinet quit!\n", inet_ntoa(s_addr.sin_addr)); exit(0); } else { printf("IP: %s %s\n", inet_ntoa(s_addr.sin_addr), r_buf); } } } else { while(1){ gets(w_buf); n_write = write(c_fd, w_buf, sizeof(w_buf)); if (n_write == -1){ perror("write "); } } } } } return 0; }
客户端
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <unistd.h> int main(int argc, char **argv) { int s_fd = 0; int ret = 0; char r_buf[128]; char w_buf[128]; int n_read; int n_write; struct sockaddr_in addr; if (argc != 3){ printf("input false!\n"); exit(-1); } s_fd = socket(AF_INET, SOCK_STREAM, 0); if (s_fd == -1){ perror("socket "); exit(-1); } addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argv[2])); inet_aton(argv[1], &addr.sin_addr); ret = connect(s_fd, (const struct sockaddr*)&addr, sizeof(addr)); if (ret == -1){ perror("connect "); exit(-1); } write(s_fd, "hi server!", strlen("hi server!")); if (fork() == 0){ while(1){ memset(r_buf, 0, 128); n_read = read(s_fd, r_buf, sizeof(r_buf)); if (n_read == 0){ printf("server quit!\n"); exit(0); } else if (n_read == -1){ perror("read "); } else{ printf("get message: %s\n", r_buf); } } } else { while(1){ scanf("%s", w_buf); n_write = write(s_fd, w_buf, sizeof(w_buf)); if (n_write == -1){ perror("write "); } } } return 0; }
widow命令行telnet
telnet 192.168.1.8 8888
运行结果
这篇关于socket网络编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-23DevExpress 怎么实现右键菜单(Context Menu)显示中文?-icode9专业技术文章分享
- 2024-12-22怎么通过控制台去看我的页面渲染的内容在哪个文件中呢-icode9专业技术文章分享
- 2024-12-22el-tabs 组件只被引用了一次,但有时会渲染两次是什么原因?-icode9专业技术文章分享
- 2024-12-22wordpress有哪些好的安全插件?-icode9专业技术文章分享
- 2024-12-22wordpress如何查看系统有哪些cron任务?-icode9专业技术文章分享
- 2024-12-21Svg Sprite Icon教程:轻松入门与应用指南
- 2024-12-20Excel数据导出实战:新手必学的简单教程
- 2024-12-20RBAC的权限实战:新手入门教程
- 2024-12-20Svg Sprite Icon实战:从入门到上手的全面指南
- 2024-12-20LCD1602显示模块详解