17

我想知道是否有一种简单的方法可以遍历 fd_set?我想这样做的原因是不必遍历所有连接的套接字,因为 select() 将这些 fd_set 更改为仅包含我感兴趣的那些。我也知道,使用不打算直接访问的类型的实现通常是一个坏主意,因为它可能因不同的系统而异。但是,我需要一些方法来做到这一点,而且我的想法已经不多了。所以,我的问题是:

如何遍历 fd_set?如果这是一个非常糟糕的做法,除了遍历所有连接的套接字之外,还有其他方法可以解决我的“问题”吗?

谢谢

4

7 回答 7

11

您必须在调用 select() 之前填写 fd_set 结构,您不能直接传入原始的 std::set 套接字。select() 然后相应地修改 fd_set,删除所有未“设置”的套接字,并返回剩余的套接字数。您必须遍历生成的 fd_set,而不是您的 std::set。不需要调用 FD_ISSET() 因为生成的 fd_set 只包含准备好的“set”套接字,例如:

fd_set read_fds;
FD_ZERO(&read_fds);

int max_fd = 0;

read_fds.fd_count = connected_sockets.size();
for( int i = 0; i < read_fds.fd_count; ++i ) 
{
    read_fds.fd_array[i] = connected_sockets[i];
    if (read_fds.fd_array[i] > max_fd)
      max_fd = read_fds.fd_array[i];
}

if (select(max_fd+1, &read_fds, NULL, NULL, NULL) > 0)
{ 
    for( int i = 0; i < read_fds.fd_count; ++i ) 
        do_socket_operation( read_fds.fd_array[i] ); 
} 

FD_ISSET() 更常发挥作用的地方是在使用 select() 进行错误检查时,例如:

fd_set read_fds;
FD_ZERO(&read_fds);

fd_set error_fds;
FD_ZERO(&error_fds);

int max_fd = 0;

read_fds.fd_count = connected_sockets.size();
for( int i = 0; i < read_fds.fd_count; ++i ) 
{
    read_fds.fd_array[i] = connected_sockets[i];
    if (read_fds.fd_array[i] > max_fd)
      max_fd = read_fds.fd_array[i];
}

error_fds.fd_count = read_fds.fd_count;
for( int i = 0; i < read_fds.fd_count; ++i ) 
{
    error_fds.fd_array[i] = read_fds.fd_array[i];
}

if (select(max_fd+1, &read_fds, NULL, &error_fds, NULL) > 0)
{ 
    for( int i = 0; i < read_fds.fd_count; ++i ) 
    {
        if( !FD_ISSET(read_fds.fd_array[i], &error_fds) )
            do_socket_operation( read_fds.fd_array[i] ); 
    }

    for( int i = 0; i < error_fds.fd_count; ++i ) 
    {
        do_socket_error( error_fds.fd_array[i] ); 
    }
} 
于 2010-09-07T19:36:11.177 回答
7

Select 设置与集合中的文件描述符相对应的位,因此,如果您只对少数几个感兴趣(并且可以忽略其他),则无需遍历所有 fds 只需测试您感兴趣的那些文件描述符.

if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
   perror("select");
   exit(4);
}

if(FD_ISSET(fd0, &read_fds))
{
   //do things
}

if(FD_ISSET(fd1, &read_fds))
{
   //do more things
}

编辑
这是 fd_set 结构:

typedef struct fd_set {
        u_int   fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

其中,fd_count 是设置的套接字数量(因此,您可以使用它添加优化),fd_array 是位向量(大小为 FD_SETSIZE * sizeof(int) ,取决于机器)。在我的机器中,它是 64 * 64 = 4096。

因此,您的问题本质上是:在位向量(大小约为 4096 位)中找到 1 的位位置的最有效方法是什么?

我想在这里澄清一件事:
“循环通过所有连接的套接字”并不意味着您实际上正在读取/对连接进行操作。FD_ISSET() 仅检查 fd_set 中位于连接分配的 file_descriptor 编号的位是否已设置。如果效率是您的目标,那么这不是最有效的吗?使用启发式?

请告诉我们这种方法有什么问题,以及您试图使用替代方法来实现什么。

于 2010-09-07T18:18:49.337 回答
4

这很简单:

for( int fd = 0; fd < max_fd; fd++ )
    if ( FD_ISSET(fd, &my_fd_set) )
        do_socket_operation( fd );
于 2010-09-07T18:17:02.583 回答
4

这种循环是select()接口的限制。的底层实现fd_set通常是位设置的,这显然意味着寻找套接字需要扫描位。

正是出于这个原因,已经创建了几个替代接口 - 不幸的是,它们都是特定于操作系统的。例如,Linux 提供了epoll,它只返回一个活动文件描述符的列表。FreeBSD 和 Mac OS X 都提供了kqueue,实现了相同的结果。

于 2010-09-08T02:36:48.853 回答
1

请参阅Beej网络指南的第 7.2 节 - '7.2。select() - 使用 FD_ISSET 的同步 I/O 多路复用。

简而言之,您必须遍历 fd_set 以确定文件描述符是否已准备好进行读/写...

于 2010-09-07T18:14:23.900 回答
0

我不认为你试图做的是一个好主意。

首先它依赖于系统,但我相信你已经知道了。

其次,在内部级别,这些集合存储为整数数组,fds 存储为集合位。现在根据 select 的手册页,FD_SETSIZE 是 1024。即使您想迭代并获得您感兴趣的 fd,您也必须遍历该数字以及位操作的混乱。因此,除非您在 select 上等待超过 FD_SETSIZE fd,我认为这是不可能的,否则这不是一个好主意。

等一下!!。无论如何,这不是一个好主意。

于 2010-09-07T18:31:31.603 回答
0

我认为您无法select()有效地使用呼叫。“ C10K 问题”中的信息仍然有效。

您将需要一些特定于平台的解决方案:

或者您可以使用事件库为您隐藏平台详细信息libev

于 2013-03-15T20:58:48.427 回答