目录select()接口函数的原型返回值附带操作fd_set的四个宏select版TCP版服务器代码示例poll()接口函数原型返回值poll版TCP版服务器代码示例网络编程常用函数socket()bind()inet_addr()htons() htonl()listen()accept()为每个客户端都开一个线程虽然能够提高实时性 但是非常浪费资源并且具备一定风险所以使用多路IO复用IO复用Input/Output Multiplexing是一种高效的技术用于在单个线程中管理多个输入/输出通道。它允许程序同时监听多个IO事件如网络套接字、文件描述符等并在有事件发生时进行处理而无需为每个通道创建独立的线程或进程。通过IO复用程序可以避免阻塞在单一IO操作上从而提升系统的并发性能select()接口函数的原型intselect(intnfds, //所有被监控的 fd 中最大值 1fd_set*readfds, //可读文件描述符集合fd_set*writefds, //可写文件描述符集合fd_set*exceptfds, //异常事件文件描述符集合structtimeval*timeout // 超时时间填NULL表示无限阻塞直到有事件发生);返回值成功返回所有集合中就绪 fd 的总数超时返回 0失败返回 -1附带操作fd_set的四个宏voidFD_SET(intfd,fd_set*set);//往集合里面添加一个文件描述符voidFD_CLR(intfd,fd_set*set); //从集合里面 删除一个指定的文件描述符intFD_ISSET(intfd,fd_set*set); //查看集合里面是否存在这个文件描述符voidFD_ZERO(fd_set*set);// 清空整个集合使用前调用select版TCP版服务器代码示例#include sys/select.h #include sys/time.h #include sys/types.h #include unistd.h #include stdio.h #include stdint.h #include stdlib.h #include string.h #include fcntl.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/stat.h int main(void) { // 1.创建套接字 int socket_fd socket(AF_INET, SOCK_STREAM, 0); if(-1 socket_fd) { perror(socket_fd); return -1; } // 2.设置端口复用 int opt 1; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); setsockopt(socket_fd, SOL_SOCKET, 15, opt, sizeof(opt)); // 3.绑定IP和端口号 struct sockaddr_in addr {0}; addr.sin_addr.s_addr 0; // 万用IP addr.sin_family AF_INET; // IPV4 addr.sin_port htons(6000); // 端口号 int result bind(socket_fd, (struct sockaddr*)addr, sizeof addr); if(result 0) // 成功result 0失败result -1 { perror(bind:); close(socket_fd); return -1; } // 4.设置监听数 listen(socket_fd, 16); printf(服务器已开启\r\n); //------------------------------------------------------------------ // 1. 创建一个读表 int max_fd 0; max_fd socket_fd; // 目前服务器套接字为最大的描述符 fd_set readfds, backupsfds; // 读表和备份 // 2. 清空读表 FD_ZERO(readfds); // 3. 把服务器套接字添加进读表 FD_SET(socket_fd, readfds); // 4. 备份读表 backupsfds readfds; // 5. 循环阻塞检测 while(1) { readfds backupsfds; result select(max_fd1, readfds, NULL, NULL, NULL); if(result 1) // 至少有一个监测的文件发生了事件 { for(int i socket_fd; i max_fd; i) // 遍历读表 { if(FD_ISSET(i, readfds)) // 找到出现事件的文件 { if(i socket_fd) // 服务器套接字出现事件 { // 接受客户端的连接 int client_fd accept(socket_fd, NULL, NULL); // 将连接的客户端的套接字描述符加入读表 FD_SET(client_fd, backupsfds); max_fd max_fd client_fd ? client_fd : max_fd; printf(客户端%d连接!\n, client_fd); } else // 客户套接字事件 { char buf[1024] {0}; int len read(i, buf, sizeof buf); if(0 len) // 客户端离线 { close(i); FD_CLR(i, backupsfds); if(max_fd i) max_fd--; printf(客户端%d离线...\n, i); continue; // } printf(客户端%d-%s\n, i , buf); } } } } } return 0; }poll()接口函数原型#include poll.h struct pollfd { int fd; /* 要监控的文件描述符 */ short events; /* 输入参数需要监测的事件 */ short revents; /* 输出参数内核返回的、实际发生的事件 */ }; int poll(struct pollfd *fds, // 指向pollfd结构体数组的数组名 nfds_t nfds, // 第一个参数数组的元素个数即要监控多少个描述符 int timeout // 等待的超时时间单位是ms,填-1为死等 );返回值成功返回有事件发生的文件描述符个数超时返回0失败返回-1poll版TCP版服务器代码示例#include sys/select.h #include sys/time.h #include sys/types.h #include unistd.h #include stdio.h #include stdint.h #include stdlib.h #include string.h #include fcntl.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/stat.h #include poll.h #define SERVER_IP 192.168.61.130 #define SERVER_PORT 60000 int main(void) { // 1. 创建服务器套接字 int socket_fd socket(AF_INET, SOCK_STREAM, 0); if(socket_fd 0) { perror(socket_fd); return -1; } // 2. 绑定地址信息 struct sockaddr_in addr {0}; addr.sin_addr.s_addr inet_addr(SERVER_IP); addr.sin_port htons(SERVER_PORT); addr.sin_family AF_INET; int result bind(socket_fd, (struct sockaddr*)addr, sizeof addr); if(result ! 0) { perror(bind); return -1; } // 3. 监听 listen(socket_fd, 10); printf(服务器已开启 IP-%s PORT-%d\n, SERVER_IP, SERVER_PORT); //-------------------------------------------------------------------- // 1. 创建一个poll需要的监测结构体数组 struct pollfd fd_array[10] {0}; uint8_t fd_array_itemCount 0; // 数组中成员的总数 // 2. 添加服务器套接字描述符至数组 fd_array[0].fd socket_fd; fd_array[0].events POLLIN; // 有数据可读(有数据输入) fd_array[0].revents 0; // 实际上发生的事件类型 fd_array_itemCount; // 3.轮训监测 while(1) { result poll(fd_array, fd_array_itemCount, -1); if(result 0) // 监测的文件有事件发生 { for(int i 0; i fd_array_itemCount; i) { // 找到触发事件的文件 if(fd_array[i].events fd_array[i].revents) { if(fd_array[i].fd socket_fd) // 如果是服务器 { // 接受客户端的连接 int client_fd accept(socket_fd, NULL, NULL); // 添加客户端套接字描述符至结构体数组 fd_array[fd_array_itemCount].fd client_fd; fd_array[fd_array_itemCount].events POLLIN; fd_array[fd_array_itemCount].revents 0; fd_array_itemCount; printf(客户端%d连接成功..\n, client_fd); } else // 客户端 { char buf[1024] {0}; int len read(fd_array[i].fd, buf, sizeof buf); if(len 0) // 客户端离线 { printf(客户端%d离线...\n, fd_array[i].fd); // 1. 关闭客户端套接字 close(fd_array[i].fd); // 2. 从数组中删除客户端套接字描述符 for(int j i; j fd_array_itemCount; j) { fd_array[j] fd_array[j1]; } // 3. 更新数组中成员的总数 fd_array_itemCount--; } else // 正常收到消息 { printf(收到客户端%d信息-%s\n, fd_array[i].fd, buf); } } } } } } return 0; }网络编程常用函数socket()作用创建一个socket套接字#include sys/socket.h int socket( int domain, // 协议族AF_INET 用于 IPv4AF_INET6 用于 IPv6 int type, // 套接字类型SOCK_STREAM 用于 TCPSOCK_DGRAM 用于 UDP int protocol // 通常填 0让系统自动选择协议 );返回值成功返回文件描述符0失败返回-1bind()作用用于绑定套接字的IP属性和端口号#include sys/types.h #include sys/socket.h int bind( int sockfd, // 已经创建但尚未绑定的套接字 const struct sockaddr *addr, // 要绑定的 IP 地址和端口号 socklen_t addrlen // addr结构体的长度一般用sizeof() );返回值0 绑定成功-1绑定失败示例// 2. 绑定地址信息 struct sockaddr_in addr {0}; addr.sin_addr.s_addr inet_addr(192.168.130.31); addr.sin_port htons(55555); addr.sin_family AF_INET; int result bind(socket_fd, (struct sockaddr*)addr, sizeof addr); if(result ! 0) { perror(bind); return -1; }inet_addr()inet_addr()是一个将点分十进制 IPv4 地址字符串如192.168.1.1转换为 32 位网络字节序整数的函数函数原型#include arpa/inet.h // Linux 下的头文件 in_addr_t inet_addr(const char *cp);返回值将字符串IP装换为大端的32位数字IP示例addr.sin_addr.s_addr inet_addr(192.168.130.31);htons() htonl()将16 或32 位整数从主机字节序转换为网络字节序host to network short / long函数原型#include arpa/inet.h // Linux #include winsock2.h // Windows uint16_t htons(uint16_t hostshort); uint32_t htonl(uint32_t hostlong);listen()listen()开启监听且设置最大监听数量函数原型#include sys/types.h #include sys/socket.h int listen(int sockfd, int backlog);返回值0监听成功-1失败accept()函数功能用于接受客户端的连接。函数原型intaccept(intsockfd, //服务器的套接字structsockaddr*addr, // 存放客户端地址信息的结构体地址socklen_t*addrlen // 上一个参数的大小sizeof(addr))返回值成功返回客户的 通信套接字失败错误码如有错误或不完善之处 请不吝赐教。