Windows下select支持任意句柄,Linux仅支持socket;Linux需设非阻塞且不可用于普通文件,select会修改fd_set和timeval,每次调用前须FD_ZERO+FD_SET并重置超时。

select 在 Windows 和 Linux 上行为不一致,先确认你的平台
Windows 下 select 可用于任意句柄(包括文件、管道、控制台),而 Linux 仅支持 socket 类型的文件描述符。如果你在 Linux 上对普通文件或 stdin 调用 select,它会直接返回 -1 并设 errno 为 EBADF —— 这不是 bug,是 POSIX 规定。
常见误判:看到 select 返回 0 就以为“超时”,其实也可能是被信号中断(errno == EINTR),尤其在未屏蔽信号的服务器中很常见。
- Linux 下务必只对 socket fd 使用
select,且需提前用fcntl(fd, F_SETFL, O_NONBLOCK)设为非阻塞,否则recv/send可能卡住 - Windows 下可用
WSAEventSelect替代,更轻量;若坚持用select,注意fd_set中的 socket 必须是异步模式(WSAAsyncSelect或ioctlsocket(..., FIONBIO, &nonzero)) -
select的最大监控 fd 数受限于FD_SETSIZE(通常为 64 或 1024),不能靠增大数组绕过 —— 它是编译期宏,改了要重编译 libc 或 CRT
fd_set 初始化和重置必须每次循环都做
select 会修改传入的 fd_set,把就绪的 fd 对应位保留,其余清零。这意味着:下一轮调用前不重新 FD_ZERO + FD_SET,就会漏掉未就绪的 fd。
典型错误写法:
fd_set readfds; FD_ZERO(&readfds); FD_SET(sockfd, &readfds); // 循环里直接 reuse readfds —— 错!
- 每次调用
select前,都要重新构造fd_set:先FD_ZERO,再对每个活跃 socket 调用FD_SET - 不要把
fd_set当作“状态缓存”,它只是临时输入参数;就绪状态得靠返回值和遍历判断 - 如果 socket 数量多(比如几百个),反复
FD_SET开销不小,这时该考虑epoll(Linux)或IOCP(Windows)
如何正确判断哪个 socket 就绪
select 返回后,不能直接用 FD_ISSET(sockfd, &readfds) 判断 —— 因为它只告诉你“这个 fd 在集合里且就绪”,但你得确保这个 fd 确实属于当前监控集合,且没被其他线程关闭。
安全做法是:遍历所有你关心的 socket,逐个检查是否在返回的 fd_set 中,并立刻处理(如 recv)。
- 别跳过
FD_ISSET检查直接读 —— 若 socket 已关闭但 fd 未从集合中移除,recv会返回 0 或 -1,容易引发逻辑错乱 - 收到
recv返回 0 表示对端关闭,此时应立即close并从监控列表中移除该 socket - 若
recv返回 -1 且errno是EAGAIN或EWOULDBLOCK,说明数据已读完,可继续处理其他 socket
select 的 timeout 参数容易被误解
struct timeval 是**值传递**,select 会修改它,表示剩余等待时间。很多代码把它定义为局部变量并复用,结果第二次调用时 timeout 变成 0 —— 看似“不阻塞”,其实是上一轮被改掉了。
- 要么每次调用前重新初始化
timeout(推荐):timeval tv = {1, 0}; // 1 秒 - 要么用指针传 NULL 实现永久阻塞,但服务端一般不这么做
- 注意:Linux 下
select修改tv是标准行为;Windows 的select不修改(但为了跨平台,仍建议每次重置)
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











