【问题标题】:More than 1024 clients using fd_set?超过 1024 个客户端使用 fd_set?
【发布时间】:2018-02-28 14:39:16
【问题描述】:

我使用select() 来衡量服务器没有收到任何新消息的时间。代码很简单,看起来像这样:

int res = -1;
do {
    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    res = select(maxfd+1, &readfds, NULL, NULL, &tv);
    gettimeofday(&current, NULL);
    if (get_time_diff(current, last) >= diff) {
        // do something
    }
} while (res <= 0);

当然,这不适用于超过 1024 个连接,因为fd_set 只有 1024 位。幸运的是,我不需要存储所有的 FD,我只需要知道何时发生新连接。所以我用服务器 FD 之后的下一个数字替换了maxfd+1(它总是相同的,在我的情况下等于 18)。

现在一切似乎都很好,所有客户端都从服务器收到了正确的消息。但是,我不确定它是否是一个有效的解决方案,而且它肯定不是一个非常干净的解决方案。这会导致我不知道的任何问题吗?

【问题讨论】:

  • 您是否建议接受套接字上的活动将唤醒仅对侦听套接字敏感的 select 调用?这绝对不是一个安全的假设。 select() 几乎已经失宠,更好的选择是poll(便携式)和epoll(Linux)或完成端口(Windows)。
  • @BenVoigt 当然,没有任何活动,但我认为如果连接是新的,它应该可以工作。在我的应用程序中,每个客户端只连接一次并发送一条消息(总是),所以我想我可以做出这个假设。好像对监听socket加一个新socket比较敏感。
  • 是的,这很好,尽管在 TCP 中,数据通常与用于三次握手的数据报是分开的......然后如果你只是 select()-ing 在侦听套接字上,您最终可能会卡在新连接上的同步 recv() 中,同时无法处理其他连接尝试。有一个接受队列,所以你不会失去客户,但它可能会打乱时间。 (以及连接然后发送零消息的恶意客户端......)
  • 您确定问题应该标记为c++吗?对我来说看起来像 c...
  • @Myst:这是很好的 C++ 代码。

标签: c++ sockets


【解决方案1】:

是的,select 受设计限制,与 fd 值超过 1023 一起使用是不安全的。

老实说,您可能找不到使用 select 的重负载生产服务器(如果有,那么您可能不应该使用它们)。

所以我将 maxfd+1 替换为服务器 FD 之后的下一个数字(始终相同,在我的情况下等于 18)。

我假设您的示例是一个最小示例,并且您不只是查看单个收听的 fd(否则,您可能会阻止 accept,这更有意义)。

这会提醒您打开了另一个文件描述符*...但是,我觉得这不是最好的方法,因为:

  • 它只会对第一个文件描述符这样做......

    新客户端将根据第一个客户端的状态运行,它们自己的状态将被忽略,因为它们的fd 的状态从未经过测试。

  • 它使您的代码既脆弱又僵硬。

  • 其他文件描述符(非套接字)可能会占用特定的文件描述符,从而导致实现非常糟糕。

  • 如果将文件描述符添加到集合中,您仍然需要测试它们的值。

更好的解决方案可能包括:

  • 测试客户端的 fd 值并强制限制 (if (fd &gt;= 1024) close(fd))。

  • 使用 poll(老实说,这就是它存在的原因 - 引入它是为了解决 select 施加的限制)。

  • 使用操作系统特定的 API(epoll 用于 Linux,kqueue 用于 BSD/macOS 等)。

  • 使用抽象出操作系统特定 API 的库(例如,libev)。

祝你好运!


* Unix 风格的系统保证任何新的文件描述符都将被分配最低的fd 可用值。

这意味着您的第一个客户端将始终收到服务器 + 1(假设您没有打开或关闭任何其他文件描述符)。

但更新的文件描述符将被分配更高的值(除非更低的文件描述符可用)。因此,当您测试第一个客户端的状态时,永远不会轮询其他客户端。

【讨论】:

  • 我想你误解了select()的第一个参数的作用。传递maxfd+1 不会使select() 对文件描述符maxfd+1 上的事件敏感,即使这是选择的下一个描述符。当侦听套接字准备好调用accept() 时,问题中的select() 调用会唤醒——而不是新fd 的数值(它甚至在之后 之前都不存在accept()反正),是检测新连接的机制。
  • @BenVoigt,我知道这一点,但它确实限制了 FD_SET,因此它忽略了所有高于maxfd + 1 的文件描述符。它无法判断客户端何时有待处理数据或可用于写入。
  • select() 调用无法判断客户端何时有待处理的数据,因为 FD_ZERO 将它们排除在敏感集之外。
  • @BenVoigt - 我认为示例代码只是一个简短的示例。如果 OP 只想测试监听套接字,而不是(我假设),OP 只会阻塞accept 而不是等待select。这个假设是我的答案有这些额外细节的原因。很明显,示例中只向集合中添加了一个 sockfd,但我假设实际代码添加了更多。也许我错了。
  • 我没有使用阻塞 accept() 正是因为它只是等待新的连接,如果等待时间超过限制,我将无能为力。实际上,非阻塞accept() 似乎正在工作。我只是没想到。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
  • 2010-12-04
  • 2013-09-30
  • 2016-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多