select调用中为什么不直接遍历 readfds,而要遍历 client_sockets?

/ 默认分类 / 0 条评论 / 9浏览

为什么不直接遍历 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;

但关键问题是:

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):


总结

select 调用返回时,readfds 中确实只包含就绪的 fd,但它本质是一个位图结构,没有显式“fd 列表”,
所以程序必须借助自己维护的 client_sockets[] 列表来判断哪些 fd 是就绪的。