如何从select()函数返回的fd_set结果中仅循环遍历活动文件描述符?

7

所以在我的当前服务器实现中,它目前是这样的:

  void loop(){
     // step 1: clear set

     fd_set readfds;

     while(true){

        // step 1:
        FD_ZERO(readfds);

        // step 2:
        loop_through_sockets_and_add_active_sockets_to(theset);

        // step 3:
        switch(select(FD_SETSIZE, &readfds, 0, 0, &tv)) {
           case SOCKET_ERROR:
              patia->receiveEvent(Error, net::getError());
              return;
           case 0:
              return;
        }

        // step 4:
        loop through sockets and check, using FD_ISSET, 
        which read fd's have incoming data.

     }
  }

现在,不清除fd_set(只使用FD_SET、FD_CLR在添加/删除通道时)将是更好的做法。

我的问题是,如何在select()之后循环遍历fd_set,而不检查集合中的每个成员是否属于该集合,而又不使用FD_ISSET?

我的意思是,当您有4000个活动连接时,每当有传入数据时,上述循环将不得不在到达正确连接之前通过潜在的4000个套接字。如果所有线程都很活跃,复杂性将是n^2!


2
请使用 poll(2)(或 epoll)代替。 - William Pursell
3个回答

6
我的问题是,在select()之后如何循环遍历fd_set,而不检查集合中的每个成员是否是集合的一部分,也不使用FD_ISSET?你不能这样做。有一个小优化,即select()返回准备好的描述符数量,因此,如果您保持已处理的数量计数,那么当您知道已完成所有操作时,可以停止而不必到达集合的末尾。
我的意思是,当您有4000个活动连接时,每当有传入数据时,上述循环都必须通过潜在的4000个套接字才能到达正确的套接字。如果所有线程都非常活跃,则复杂度将为n ^ 2!我不明白您从哪里得到O(n^2)。毕竟,从select()返回后,您会逐个处理每个已就绪的描述符。如果您有4000个就绪的IO描述符,则通过循环遍历内存中的4000个C对象数组的开销将相当微不足道。

说得好 - 我想连接越繁忙,循环就会更有效率(因为它可以处理每个连接更多的数据)。我想我也可以在这里问一下:在那个循环中,FD_ZERO是必要的吗,还是我误解了? - kamziro
1
@kamziro:FD_ZERO将整个集合初始化为空集。你可能只想在准备使用select函数添加文件描述符到集合之前执行此操作,而绝对不是在测试文件描述符的循环内部执行。 - JeremyP
@kamziro:我认为你实际上可以在不检查每个成员的情况下迭代fd_set。请查看我的答案。 - MaPePeR
@JeremyP 有没有一种方法可以从选择集中提取文件描述符? - Bionix1441

4
你可以。这将在不使用数组中的 ISSET() 的情况下迭代集合。 但是,你仍然需要触及 fd_set.__fds_bits 数组中的所有 long
#include<sys/select.h>
#include<stdio.h>
int main(void)
{
    fd_set fds;
    FD_ZERO(&fds);
    //Fill the set.
    FD_SET(6, &fds);FD_SET(20, &fds);FD_SET(33, &fds);FD_SET(200, &fds);
    int i;
    unsigned long *m = (unsigned long *)__FDS_BITS(&fds);
    int fd=0;
    for (i = 0; i < sizeof (fd_set) / sizeof (unsigned long); ++i) //can use int, long or long long. Using long because internal structure is long.
    {
        fd=sizeof (unsigned long)*i*8;
        while(m[i]!=0)
        {
            fd+=__builtin_ctzl(m[i]); //Get Number of trailing zero bits in long.
            printf("FD=%d\n",fd);
            /*Found FD*/
            m[i]>>=(__builtin_ctzl(m[i]))+1; 
            ++fd;
        }
    }
    return 0;
}

使用gcc (SUSE Linux) 4.6.2,这对我来说很好用。


背景

在我的系统中,fd_set看起来是这样的(摘自并简化了/usr/include/sys/select.h):

typedef struct {
    __fd_mask __fds_bits[__FD_SETSIZE/__NFDBITS];
}

使用__fd_mask作为long int的类型定义。
在我的系统上,__FD_SETSIZE/__NFDBITS似乎是16
所以这个数组是(__FD_SETSIZE/__NFDBITS)*sizeof(__fd_mask)*8位。
随着__NFDBITS = 8*sizeof(__fd_mask),你可以看到,它包含__FD_SETSIZE位。

查看/usr/include/bits/select.h中实际宏的定义,发现__fd_bits用于存储fd。当设置fd n时,在__fd_bits的第n位设置为1。

i386(among others)处理器具有计算数字尾随0位的单个操作。通过这些尾随0位数目,您可以将__fd_bits条目轻松移位该数量+1,并找到下一个true位。

与循环遍历fd_set的条目相比,如果不使用select的返回值进行优化,则至少需要进行__FD_SETSIZE/__NFDBITS=16次循环。
但要确保您的处理器和编译器支持所选择的循环类型的操作。如果它默认不使用位运算,而是使用复杂的实现,可能会变得更糟。
必须证明这是否比循环已知的fds更好。

3
很可能您已经有与每个正在进行 select() 的打开文件描述符相关联的数据结构。当从 select() 返回的 fd_set 进行去多路复用时,您需要引用它。
如果文件描述符的数量明显小于 FD_SETSIZE,那么最好遍历它(例如,只是打开的文件描述符),并使用 FD_ISSET() 检查活动情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接