如何遍历fd_set

时间:2010-09-07 17:56:07

标签: c++ c select file-descriptor

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

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

由于

7 个答案:

答案 0 :(得分:11)

在调用select()之前必须填写一个fd_set结构,不能直接传入原始的std :: socket套接字。 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] ); 
    }
} 

答案 1 :(得分:6)

选择设置与集合中的文件描述符对应的位,因此,如果您只对少数(并且可以忽略其他人)感兴趣,则需要 - 不迭代所有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()仅检查位于连接的已分配file_descriptor编号的fd_set中的位是否已设置。如果效率是你的目标,那么这不是最有效的吗?使用启发式?

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

答案 2 :(得分:4)

这是相当直截了当的:

for( int fd = 0; fd < max_fd; fd++ )
    if ( FD_ISSET(fd, &my_fd_set) )
        do_socket_operation( fd );

答案 3 :(得分:3)

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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

我不认为你想要做的是一个好主意。

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

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

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

答案 6 :(得分:0)

我认为您无法有效使用select()电话。 “The C10K problem”的信息仍然有效。

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

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