为什么不直接遍历 readfds,而要遍历 client_sockets?
因为 fd_set 是一个位图(bitmap),不包含“具体有哪些 fd”的显式列表,无法直接遍历。
只能遍历你自己保留的“fd 列表”(如 client_sockets[]),然后一个个用 FD_ISSET(fd, &readfds) 判断该 fd 是否就绪。
一、fd_set 的数据结构本质(为什么不能遍历)
fd_set 在 Linux 中本质是一个定长的 bitmap,例如(64 位系统):
#define __FD_SETSIZE 1024
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(unsigned long))];
} fd_set;
- 每个位(bit)代表一个 fd 是否被设置监听
- fds_bits 数组只是一个简单的位数组,不记录 “哪些 fd 是就绪的”
- 内核修改这些位:将“未就绪的 fd”的位清零,只保留就绪的 fd 对应的位
但关键问题是:
fd_set 并没有提供“遍历哪些位是 1”的接口。你必须自己遍历 0~1023 的整数,看哪些位是被设置的。
二、为什么遍历 client_sockets 更高效?
在你的程序中:
int client_sockets[MAX_CLIENTS]; // 显式保存当前已连接的客户端 fd 列表
你只需要在 MAX_CLIENTS 范围内遍历 client_sockets[i] 中的值,然后通过:
if (FD_ISSET(client_sockets[i], &readfds)) {
// 表示该客户端 fd 就绪了
}
就能高效判断哪个 fd 是就绪的。
相比之下,如果你要遍历整个 fd_set:
for (int fd = 0; fd < 1024; ++fd) {
if (FD_ISSET(fd, &readfds)) {
// 这个 fd 就绪
}
}
即便你的客户端连接最多只有 10 个,你仍然要扫描 0~1023 的全部 fd,造成严重的资源浪费。
三、两种方式对比总结
遍历方式 | 是否可行 | 效率 | 是否推荐 |
---|---|---|---|
遍历 readfds 的所有可能位 | 可以,但必须 0~1023 全扫描 | 低效 | 不推荐 |
遍历程序自己维护的 client_sockets[] | 可行 | 高效(已知连接) | 推荐 |
四、为什么 epoll 能避免这种问题?
epoll 内核中有一个就绪事件列表(ready list):
- 你调用 epoll_wait() 得到的就是真正就绪的 fd 数组
- 无需再遍历你自己维护的所有 fd
- 这就是 epoll 比 select 更高效的一个核心点
总结
select 调用返回时,readfds 中确实只包含就绪的 fd,但它本质是一个位图结构,没有显式“fd 列表”,
所以程序必须借助自己维护的 client_sockets[] 列表来判断哪些 fd 是就绪的。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名,转载请标明出处
最后编辑时间为:
2025/05/16 21:55