C++多客户端WebSocket应用Linux下select()无法检测客户端数据问题
问题根因及修复方案
核心问题点
- 异步设置fd_set存在时序/线程安全问题:你在
FD_ZERO后启动异步线程setting_file_thread向fr集合添加客户端套接字,但select调用和异步线程执行没有同步机制,Linux下调度逻辑和Windows不同,大概率select执行时异步线程还没完成客户端fd的添加,导致fr集合里只有服务端监听套接字,自然只能检测到连接请求。 - 局部
std::future的隐式阻塞问题:fu_1是if块内的局部变量,块结束时future自动析构,对于std::launch::async启动的任务,future析构会阻塞等待任务执行完成,就算你等到任务完成,如果max_fd没有更新,超过max_fd的客户端fd也不会被select处理。 max_fd未正确维护:你每次新增客户端套接字到队列后,没有更新max_fd为当前所有监听套接字(服务端+所有客户端)中的最大值,select的第一个参数是监听的最大fd+1,超过这个值的fd即使加到fd_set里也不会被检测。- fd_set操作线程不安全:如果
fr是类成员变量,多线程同时操作fd_set结构会导致内部数据错乱,即使加了客户端fd也无法被正确识别。 - 读事件未遍历处理:select返回后你只判断了服务端套接字的事件,else分支没有遍历所有客户端套接字判断哪个触发了读事件,就算有事件也无法正确对应处理。
修复建议
- 去掉异步设置fd_set的逻辑,直接在主线程中遍历客户端队列,把所有客户端fd逐一
FD_SET到fr集合中,同时维护更新max_fd的值。 - select返回后如果不是服务端的连接事件,遍历所有客户端套接字,调用
FD_ISSET判断哪个有读事件,再针对对应fd做接收处理。 - 不要在事件检测分支直接开异步处理读写,避免大量连接时线程溢出,要么用线程池,要么直接在当前线程处理非阻塞读写。
修复后核心代码示例
#include <sys/ioctl.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/time.h> #include <sys/select.h> void Server::server_connection_handle (){ // 清空文件描述符集合 FD_ZERO(&fr); FD_SET(server_sock, &fr); max_fd = server_sock; // 初始化max_fd为服务端监听套接字的值 // 主线程直接添加所有客户端fd,避免异步时序问题 for (int cli_fd : queue) { FD_SET(cli_fd, &fr); if (cli_fd > max_fd) { max_fd = cli_fd; // 同步更新最大fd值 } } if (select(max_fd + 1, &fr,NULL, NULL, NULL) > 0){ if (FD_ISSET(server_sock, &fr)){ int addr_len = sizeof(sockaddr); // 接收新连接 int cli_sock = accept(server_sock , (struct sockaddr *)&addr, (socklen_t *)&addr_len); sock_listen->test_connection(cli_sock); // 新客户端加入队列 queue.emplace_back(cli_sock); std::cout << "收到来自套接字("<< cli_sock <<")的连接请求,即将处理" << std::endl; }else { // 遍历所有客户端查找触发读事件的套接字 for (auto it = queue.begin(); it != queue.end(); ) { int cli_fd = *it; if (FD_ISSET(cli_fd, &fr)) { // 替换为你自身的接收逻辑 ssize_t recv_len = recv(cli_fd, 接收缓冲区, 缓冲区大小, 0); if (recv_len <= 0) { // 连接异常/断开,关闭套接字并移除队列 close(cli_fd); it = queue.erase(it); continue; } // 处理接收到的数据逻辑 } ++it; } } } }
内容的提问来源于stack exchange,提问作者Ahmed Ezzeldin
相关产品推荐
相关产品推荐

