【问题标题】:How to loop through only active file descriptors from fd_set result from select()?如何仅循环来自 select() 的 fd_set 结果中的活动文件描述符?
【发布时间】:2011-03-29 14:33:23
【问题描述】:

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

  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!

【问题讨论】:

  • 使用 poll(2) 代替(或 epoll)

标签: c posix pipe


【解决方案1】:

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

你不能。

select()返回就绪描述符的数量有一个轻微的优化,所以如果你对你已经处理的数量进行计数,你可以在知道你已经完成它们时停止而不用走到末尾设置。

我的意思是,当您有 4000 个活动连接时,每当有传入数据时,上述循环必须经过潜在的 4000 个套接字才能到达正确的套接字。如果所有线程都非常活跃,复杂度将是 n^2!

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

【讨论】:

  • 很公平——我想连接越忙,循环的效率就越高(因为它每个连接处理更多的数据)。我想我也可以在这里问:FD_ZERO 在那个循环中是必要的,还是我被误导了?
  • @kamziro:FD_ZERO 将整个集合初始化为一个空集合。您可能只想在将文件描述符添加到集合以准备 select 函数之前执行此操作,绝对 not 在 select 完成后测试文件描述符的循环内。
  • @kamziro:我认为您实际上可以在不检查每个成员的情况下迭代 fd_set。请检查我的答案。
  • @JeremyP 有没有办法从选择集中提取文件描述符?
【解决方案2】:

你可以。
这会在没有ISSET()-ing 数组中的所有 FD 的情况下遍历集合。 但是您仍然必须触摸fd_set.__fds_bits 数组中的所有longs。

#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 bits。
使用__NFDBITS = 8*sizeof(__fd_mask),您会看到,这包含__FD_SETSIZE 位。

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

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-06
      • 1970-01-01
      • 1970-01-01
      • 2020-10-31
      • 2013-07-09
      • 1970-01-01
      • 1970-01-01
      • 2015-02-06
      相关资源
      最近更新 更多