使用select处理多端口客户端连接时阻塞问题求助
问题分析与修复方案
看起来你遇到的两个核心问题(select阻塞、依赖usleep),本质都是对select的工作机制理解不到位导致的,咱们一步步拆解问题:
核心错误点
1. 没有重新初始化fd_set
select函数会修改传入的读集合set:它会把没有触发事件的文件描述符从集合中清除,只保留有事件的fd。你现在只在循环外初始化了一次set,第一次select触发后,set里只剩下触发事件的fd,后续循环的select就只会监听这些fd,自然会阻塞在没有新事件的状态。
2. 错误判断已连接客户端的事件
你用i == new_comm和i == new_serv来判断已连接套接字的事件,但new_comm/new_serv只会保存最后一次接受的连接fd,之前的客户端fd完全不会被处理,这会导致除了最后一个客户端,其他连接都无法响应。
3. 缺少对已连接fd的持久化管理
你没有维护一个保存所有已连接客户端fd的集合,每次循环无法把这些fd重新加入select的监听集合,导致后续无法监听这些客户端的读写事件。
修复后的代码示例
首先,我们需要一个全局或静态的集合来保存所有需要监听的fd(包括两个监听套接字和所有已连接客户端),然后每次循环前重新初始化select的集合:
// 定义全局集合保存所有需要监听的fd fd_set all_fds; int max_fd; int main() { // 这里省略初始化监听套接字fd_serv、fd_comm的代码... FD_ZERO(&all_fds); FD_SET(fd_serv, &all_fds); FD_SET(fd_comm, &all_fds); max_fd = max(fd_serv, fd_comm); while (1) { // 每次循环都要重新创建select的集合,从all_fds复制(避免select修改原始集合) fd_set read_set = all_fds; int ret = select(max_fd + 1, &read_set, NULL, NULL, NULL); if (ret < 0) { perror("select"); exit(EXIT_FAILURE); } // 遍历所有可能的fd,检查是否有事件触发 for (int i = 1; i <= max_fd; i++) { if (FD_ISSET(i, &read_set)) { if (i == fd_serv) { // 处理fd_serv端口的连接请求 socklen_t sin_len_serv = sizeof(client_serv_addr); int new_serv = accept(fd_serv, (struct sockaddr *)&client_serv_addr, &sin_len_serv); if (new_serv < 0) { perror("accept"); continue; // 单个连接失败不终止整个服务 } fd_set_blocking(new_serv, 0); FD_SET(new_serv, &all_fds); if (new_serv > max_fd) { max_fd = new_serv; } } else if (i == fd_comm) { // 处理fd_comm端口的连接请求 socklen_t sin_len_comm = sizeof(client_comm_addr); int new_comm = accept(fd_comm, (struct sockaddr *)&client_comm_addr, &sin_len_comm); if (new_comm < 0) { perror("accept"); continue; } fd_set_blocking(new_comm, 0); FD_SET(new_comm, &all_fds); if (new_comm > max_fd) { max_fd = new_comm; } } else { // 处理已连接客户端的读写事件 // 这里需要区分当前fd属于哪个监听端口的客户端,你可以维护一个映射表(比如数组)来记录 if (is_comm_client(i)) { // 自行实现判断逻辑,比如用数组存储每个fd的类型 if (read_command(fd_comm, i, start_time) == 1) { close(i); FD_CLR(i, &all_fds); // 可选:更新max_fd,减少后续遍历范围 if (i == max_fd) { while (max_fd > 0 && !FD_ISSET(max_fd, &all_fds)) { max_fd--; } } return SHUTDOWN; } } else { read_service(i); close(i); FD_CLR(i, &all_fds); if (i == max_fd) { while (max_fd > 0 && !FD_ISSET(max_fd, &all_fds)) { max_fd--; } } } } } } } }
关键修复说明
- 维护
all_fds集合:这个集合始终保存所有需要监听的fd(监听套接字+已连接客户端),每次循环前把它复制到read_set中传给select,避免select修改原始集合。 - 移除
usleep:之前需要usleep是因为select的集合不正确,导致程序卡在错误的监听状态,修复集合问题后,usleep完全不需要了。 - 正确判断已连接事件:只要不是两个监听套接字,就认为是已连接客户端的事件,不再依赖
new_comm/new_serv变量,这样所有客户端的事件都会被处理。 - 错误处理优化:把
exit(EXIT_FAILURE)改成continue,避免单个连接失败导致整个服务器崩溃。 - 动态更新
max_fd:当关闭客户端fd后,如果它是当前的max_fd,重新找到最大的fd,减少后续循环的遍历范围(不做也能运行,但更高效)。
另外,你需要维护一个额外的结构(比如数组或哈希表)来记录每个已连接fd属于哪个监听端口,这样在处理读写事件时能正确调用read_command或read_service。
内容的提问来源于stack exchange,提问作者Darren Col
相关产品推荐
相关产品推荐

