I/O多路复用中基于FD_SET实现多客户端写入的方法求助
多客户端消息广播问题修复方案
问题分析
你的代码存在两个核心问题,导致无法向所有客户端广播消息且buf输出异常:
- 客户端FD遍历逻辑错误:用
sockfd+j遍历客户端FD完全不成立——客户端FD并非连续分配(比如关闭某个连接后,新FD可能远大于之前的,或中间存在已关闭的无效FD);同时变量k仅记录连接次数,未剔除已断开的连接,导致循环写入无效FD。 - Buf处理存在越界风险:当
result.buf为空时,strlen(result.buf)返回0,result.buf[strlen(result.buf)-1]会访问下标为-1的非法内存,引发输出异常。
修复后的代码示例
我们需要维护一个有效的客户端FD列表,替换原有的错误遍历逻辑,同时安全处理buf内容:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/select.h> #include <netinet/in.h> #include <time.h> #define MAXLINE 1024 #define MAX_CLIENTS 100 // 设定最大支持的客户端数量 struct result { char buf[MAXLINE]; struct tm tm; }; int main() { int listen_fd, client_fd, maxfd, fd_num, addrlen; struct sockaddr_in server_addr, client_addr; fd_set readfds, allfds; struct result result; struct result *result_pointer; struct tm *lt; int i, sockfd; int client_fds[MAX_CLIENTS]; // 存储当前活跃的客户端FD int client_count = 0; // 当前活跃客户端数量 // 初始化监听socket(补全你省略的部分) listen_fd = socket(AF_INET, SOCK_STREAM, 0); memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(8080); // 示例监听端口 bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); listen(listen_fd, 5); maxfd = listen_fd; memset(client_fds, -1, sizeof(client_fds)); // 用-1标记无效FD while(1) { allfds = readfds; printf("Select Wait %d\n", maxfd); fd_num = select(maxfd + 1 , &allfds, NULL, NULL, NULL); if (FD_ISSET(listen_fd, &allfds)) { addrlen = sizeof(client_addr); client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addrlen); // 将新客户端FD加入有效列表 for (i = 0; i < MAX_CLIENTS; i++) { if (client_fds[i] == -1) { client_fds[i] = client_fd; client_count++; break; } } FD_SET(client_fd, &readfds); if (client_fd > maxfd) maxfd = client_fd; printf("Accept OK\n"); printf("maxfd : %d\n", maxfd); if (--fd_num <= 0) continue; } for (i = 0; i < MAX_CLIENTS; i++) { sockfd = client_fds[i]; if (sockfd == -1 || !FD_ISSET(sockfd, &allfds)) continue; printf("sockfd : %d\n", sockfd); memset(result.buf, 0x00, MAXLINE); ssize_t read_len = read(sockfd, &result, sizeof(result)); if (read_len <= 0) { close(sockfd); FD_CLR(sockfd, &readfds); client_fds[i] = -1; client_count--; // 若断开的是当前最大FD,重新计算maxfd优化select范围 if (sockfd == maxfd) { maxfd = listen_fd; for (int j = 0; j < MAX_CLIENTS; j++) { if (client_fds[j] > maxfd) maxfd = client_fds[j]; } } } else { if (strncmp(result.buf, "quit\n", 5) == 0) { close(sockfd); FD_CLR(sockfd, &readfds); client_fds[i] = -1; client_count--; if (sockfd == maxfd) { maxfd = listen_fd; for (int j = 0; j < MAX_CLIENTS; j++) { if (client_fds[j] > maxfd) maxfd = client_fds[j]; } } } else { result_pointer = &result; lt = &(result_pointer->tm); // 安全处理buf末尾的换行符 if (strlen(result.buf) > 0 && result.buf[strlen(result.buf)-1] == '\n') { result.buf[strlen(result.buf)-1] = '\0'; } printf("Read : %s %s", result.buf, asctime(lt)); // 向所有活跃客户端广播消息 for (int j = 0; j < MAX_CLIENTS; j++) { int target_fd = client_fds[j]; if (target_fd != -1 && target_fd != sockfd) { // 可选:跳过消息发送者自身 write(target_fd, &result, sizeof(result)); } } } } if (--fd_num <= 0) break; } } return 0; }
关键修复点说明
- 维护有效客户端FD列表:用
client_fds数组存储活跃FD,无效FD标记为-1,确保遍历的都是可用连接。 - 修正广播逻辑:遍历
client_fds数组,对每个有效FD执行write,可选择跳过消息发送者避免重复接收。 - 安全处理Buf:先判断buf长度大于0,再修改末尾换行符,彻底避免越界访问。
- 动态更新Maxfd:当断开的FD是当前最大值时,重新计算maxfd,减少select的监听范围,提升效率。
额外注意事项
read直接读取sizeof(result)要求客户端发送的数据格式与结构体完全匹配,否则会出现数据错位或粘包问题,建议改用固定长度或分隔符的方式传输数据。- 实际生产环境中,需要检查
write的返回值,处理网络繁忙导致的部分写入情况。
内容的提问来源于stack exchange,提问作者yeongbin
相关产品推荐
相关产品推荐

