如何使用select同时处理多个Socket连接?(C语言)
如何用C语言的
select函数同时处理多个Socket连接 嘿,我来给你一步步拆解怎么用C语言的select实现多Socket连接的同时读取,结合你给出的代码片段来详细讲~
先搞懂select的核心逻辑
select是IO多路复用的经典工具,它能让我们同时监控一堆文件描述符(这里就是Socket)的状态变化,不用给每个连接单独开线程或进程,能大大节省系统资源。简单说就是:告诉内核“帮我盯着这些Socket,哪个有数据可读或者有新连接了就告诉我”。
完整代码+逐段讲解
先把你给出的代码补全成可运行的版本,再一步步解释:
#include <stdio.h> #include <ctype.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int port = 3008; int listenfd; // 创建监听Socket的函数 void makelistener() { struct sockaddr_in servaddr; // 创建Socket listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) { perror("socket create failed"); exit(1); } // 设置端口复用,避免程序重启后端口被占用 int opt = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 绑定地址和端口 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡 servaddr.sin_port = htons(port); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { perror("bind failed"); exit(1); } // 开始监听,最大等待队列设为10 if (listen(listenfd, 10) == -1) { perror("listen failed"); exit(1); } printf("Listening on port %d...\n", port); } int main(int argc, char **argv) { makelistener(); int clientfd, nready; socklen_t len; struct sockaddr_in q; int i; char buf[1024]; int maxfd; fd_set allset, rset; // allset存所有要监控的fd,rset是每次select的临时集合 // 1. 初始化文件描述符集合 FD_ZERO(&allset); // 先清空集合 FD_SET(listenfd, &allset); // 把监听Socket加入集合 maxfd = listenfd; // 记录当前最大的fd,select需要这个值 while (1) { // 2. 每次调用select前,复制allset到rset(因为select会修改传入的集合) rset = allset; // 调用select,监听读事件(第二个参数是读集合),阻塞直到有事件发生 nready = select(maxfd + 1, &rset, NULL, NULL, NULL); if (nready == -1) { perror("select failed"); exit(1); } // 3. 处理新的客户端连接 if (FD_ISSET(listenfd, &rset)) { // 接受新连接 len = sizeof(q); clientfd = accept(listenfd, (struct sockaddr *)&q, &len); if (clientfd == -1) { perror("accept failed"); continue; } printf("New client connected: %s:%d\n", inet_ntoa(q.sin_addr), ntohs(q.sin_port)); // 把新的clientfd加入监控集合 FD_SET(clientfd, &allset); // 更新maxfd,如果新的fd比当前大的话 if (clientfd > maxfd) { maxfd = clientfd; } // 处理完一个事件,nready减1,减到0就不用再检查后面的fd了 if (--nready == 0) { continue; } } // 4. 处理已连接客户端的读事件 for (i = listenfd + 1; i <= maxfd; i++) { if (FD_ISSET(i, &rset)) { // 读取客户端发送的数据 int n = read(i, buf, sizeof(buf)); if (n <= 0) { // n=0说明客户端断开连接,n=-1是读错误 printf("Client disconnected: fd=%d\n", i); close(i); FD_CLR(i, &allset); // 从集合里移除这个fd } else { // 这里简单处理:把小写转大写再发回去 for (int j = 0; j < n; j++) { buf[j] = toupper(buf[j]); } write(i, buf, n); printf("Received and echoed %d bytes from fd=%d\n", n, i); } if (--nready == 0) { break; // 所有事件都处理完了,跳出循环 } } } } return 0; }
关键细节提醒
- 必须初始化集合:每次使用
fd_set前一定要用FD_ZERO清空,不然会有垃圾数据导致异常。 - 每次select要重置集合:
select会把传入的集合修改为只有就绪的fd,所以每次调用前都要从allset复制一份到临时集合rset。 - 跟踪最大fd:
select的第一个参数是maxfd + 1,内核会从0遍历到这个值,所以跟踪最大fd能提高效率。 - 及时清理断开的连接:客户端断开后要调用
close()关闭fd,并用FD_CLR从集合里移除,避免无效fd占用资源。
内容的提问来源于stack exchange,提问作者e t
相关产品推荐
相关产品推荐

