如何仅从select()的fd_set结果中循环活动文件描述符?

时间:2011-03-29 14:33:24

标签: c posix pipe

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

  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!

3 个答案:

答案 0 :(得分:6)

  

我的问题是,你如何在select()之后循环遍历fd_set,而不检查集合中的每个成员,如果它是集合的一部分,而不使用FD_ISSET?

你不能。

有一个轻微的优化,select()返回准备好的描述符的数量,所以如果你保留了你已经处理的数字的数量,当你知道你已经完成所有操作时,你可以停止,而不必走到尽头集合。

  

我的意思是,当你有4000个活动连接时,只要有传入的数据,上面的循环就必须经过4000个套接字的潜力才能找到正确的连接。如果所有线程都很活跃,复杂性将是n ^ 2!

我看不到你从哪里得到O(n ^ 2)。当然,在从select()返回之后,您将在途中处理每个准备好的描述符之后通过该集合。如果你有4,000个就绪IO描述符,那么在内存C对象中循环遍历4,000个数组的开销将是相当微不足道的。

答案 1 :(得分:3)

很可能您已经拥有与每个打开的文件描述符相关联的数据结构select()。在解复用fd_set返回的select()时,您已经需要一种引用它的方法。

如果文件描述符的数量明显小于FD_SETSIZE,那么你可能最好迭代它(例如只是打开的文件描述符)并使用FD_ISSET()来检查活动。

答案 2 :(得分:3)

你可以。
这将迭代整个集合,而不是ISSET() - 来自数组的所有FD。 但您仍需要触摸long数组中的所有fd_set.__fds_bits

#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_masklong int的typedef 我的系统__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用于存储fds。设置fd n后,nth中的__fd_bits位设置为1.

i386(在others之内)处理器只有一个操作来计算数字的尾随零位。使用此尾随零的数量,您可以轻松地将__fd_bits条目移动该数量+ 1,您将找到下一个true位。

与循环fd_set的入口集相比,当使用select的返回值进行优化时,需要至少__FD_SETSIZE/__NFDBITS=16个循环。

但请确保您的处理器和编译器支持选择循环类型的操作。当默认为NOT使用位操作时,但是复杂的实现可能会变得更糟。

如果这实际上比循环更好,则必须证明已知的fds。