【问题标题】:How to iterate through a fd_set如何遍历 fd_set
【发布时间】:2011-04-09 08:16:01
【问题描述】:

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

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

谢谢

【问题讨论】:

  • 强调我的意思。我不想使用 FD_ISSET 方法,因为它需要我遍历所有连接的套接字。但是,根据定义,select() 从集合中删除不相关的文件描述符,所以我想遍历集合。
  • 不一定表示“全部连接”。您可以将连接的套接字的子集传递给 select,然后在 select 返回后仅在该子集上使用 FD_ISSET。另外,循环遍历所有这些是否存在实际问题?除非您要处理数千个连接的套接字,否则循环可能会花费一些无关紧要的时间。
  • 同意拉基斯。这是看起来效率低下的事情之一,但在大多数情况下实际上并非如此。通过循环的时间将与仅服务一组 FD 所花费的时间相比相形见绌。
  • @Andreas: 10000 个打开的连接并且所有的事务都是串行处理的?
  • @Andreas:你这里的东西叫做瓶颈。至少可以说,除了 FD_ISSET 变慢之外,还有其他可能出错的事情。只需在几个调度程序线程之间划分连接即可。

标签: c++ c select file-descriptor


【解决方案1】:

你必须在调用 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] ); 
    }
} 

【讨论】:

  • select manpage 说:nfds is the highest-numbered file descriptor in any of the three sets, plus 1. 使用最高编号,而不是计数
  • @RemyLebeau,如果你设置了error_fds.fd_count = read_fds.fd_count;,那么如果你在if(select...)语句中只使用1个for循环会不会更好,我想检查@会有一些错误987654328@ 用于read_fdserror_fds。 (即 FD_ISSET(read_fds) 根本不检查)
  • 检查FD_ISSET(read_fds.fd_array[i], &amp;read_fds) 在此示例中将是多余的,因为循环已经在遍历read_fds 的“设置”项。循环在处理之前检查每个“可读”项目是否也在error_fds 中。要同时为两个fd_set 使用单个循环将需要不同的循环逻辑:for (int fd = 0; fd &lt;= max_fd; ++fd) { if (FD_ISSET(fd, &amp;error_fds) {...} else if (FD_ISSET(fd, &amp;read_fds)) {...} },但这不能移植到所有平台(尤其是 Windows)。
  • 当然,直接循环通过fd_count 也不是真正可移植的。 fd_set 是不透明的,你真的不应该直接访问它的元素。 FD_...() 宏旨在隐藏这些细节。
  • 我可能会循环使用connected_sockets,而不是使用for (int fd = 0; fd &lt;= max_fd; ++fd),将每一个传递给FD_ISSET()
【解决方案2】:

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 编号的位是否已设置。如果效率是您的目标,那么这不是最有效的吗?使用启发式?

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

【讨论】:

  • 也谢谢你。但是请看我的评论,也许我解释得不够清楚,这是我不想采取的方法。
  • 如果这不是[正确/你想要的]答案,为什么它被标记为答案?
  • 有两个原因。 a) 编辑提供了我正在寻找的信息 b) 我改变了主意,因此答案变得相关。
  • fd_set 本身的定义依赖于操作系统。 Linux 的 fd_set 没有 fd_count 成员。
  • 如果性能 > 可移植性,x86_64 指令集有一些指令可以真正快速地扫描机器字中的位。所以只需构造一个简单的汇编函数来进行扫描。
【解决方案3】:

这很简单:

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

【讨论】:

  • 感谢您的回答。请参阅我的评论以澄清我想要做什么。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

    • 感谢您的回答。我知道这是标准方法,但是我希望摆脱它,请参阅我对自己帖子的评论。
    【解决方案6】:

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

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

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

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

    【讨论】:

      【解决方案7】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2010-12-18
        • 2017-01-10
        • 2011-12-21
        • 2012-02-27
        • 2016-11-23
        • 2017-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多