【问题标题】:Avoid reusing of the same fd number in a multithread socket application避免在多线程套接字应用程序中重复使用相同的 fd 编号
【发布时间】:2010-12-03 00:34:15
【问题描述】:

我有一个异步应用程序执行多个线程在套接字上执行操作,这些操作被调度然后异步执行。

我试图避免这样的情况:一旦在套接字上安排了读取操作,在第一个操作开始执行之前,套接字会关闭并重新打开(可能由另一个操作中的另一个对等方),这将最终读取正确的文件描述符但错误的对等点。

问题来了是因为 (accept();close();accept()) 在两个 accept() 中返回相同的 fd,这可能导致上述情况。

我看不出有什么办法可以避免。

有什么提示吗?

【问题讨论】:

  • 为什么连接到一个线程的套接字被另一个线程关闭了?
  • 这就是异步系统的本质,一个操作读取,另一个写入,另一个可能关闭,所有这些操作都可能在不同的线程中执行。
  • 重新考虑您的方法。通常有一个线程接受连接,并产生线程来处理它们。然后这些线程负责关闭连接,并且没有其他线程可以访问该连接。
  • 我不认为 Omry 真的错了——他的观点是多个线程访问同一个套接字会带来痛苦的世界。拥有 1 个线程处理 1 个套接字的所有访问权限并不意味着您不能让 1 个线程处理多个套接字。
  • 好吧,有了 NIO(非阻塞 IO,选择/轮询和朋友),您甚至不需要多个线程来处理套接字。

标签: c++ linux multithreading sockets


【解决方案1】:

您如何管理套接字?听起来您有多个线程,其中任何一个都可以:

  1. accept 传入连接
  2. close 现有连接
  3. 建立新的传出连接

听起来您需要一种方法来调解对浮动的各种套接字的访问。您是否考虑过将每个套接字与互斥锁相关联,以防止在套接字仍在使用时关闭它,或者可能将每个套接字描述符放在一个具有原子引用计数的结构中,这将阻止其他线程关闭它,直到所有线程都完成使用它?

【讨论】:

  • 我可以这样做,但是我尽量避免向这个几乎无锁的系统添加互斥锁。最好的办法是保证 accept 不会按顺序给出 fd,但我看不到这种情况发生。
  • @Arkaitz - 那么我提到的第二个解决方案可能对你有好处。将套接字保存在具有引用计数器的结构中,并且不要让任何线程关闭引用计数大于零的套接字。没有锁,直到所有线程都完成后套接字才会关闭。
【解决方案2】:

套接字是一个 5 元组 {local-addr,local-port,remote-addr,remote-port,proto},因此如果您能够使用这些属性而不是 fd 进行事件/处理程序路由,则可以避免fd 冲突。

另一种选择是序列化所有 close()/accept() 操作(优先级?),以便它们不能混合

【讨论】:

  • 我仍然需要互斥锁来保护这些结构,这将进一步减慢系统速度,这是我试图避免锁定在那里的边缘情况。
【解决方案3】:

记录每个套接字的未决操作(读/写等),以及套接字上是否有未决的关闭请求。在您调用 close 之前,首先检查是否有任何待处理的操作。如果有,则改为调用shutdown,然后仅在未决操作计数达到0时才调用close。

【讨论】:

    【解决方案4】:

    好问题!我什至没有意识到会发生这样的问题。

    我能想到的唯一答案是您不能使用 close() 来表示套接字已终止。一种解决方案是使用 shutdown() 来终止连接。然后,您可以通过使用引用计数安全地关闭()套接字。

    【讨论】:

    • close 函数减少底层操作系统中的套接字引用计数,shutdown 强制关闭并向对等方发送 EOF / FIN。有关详细信息,请参阅此 SO 帖子:stackoverflow.com/questions/409783/…
    • 如您发布的链接中所述,shutdown() 不会关闭套接字,只会终止通信。 Shutdown() 会导致任何挂起(或未来)的 I/O 操作失败,但不会释放 FD,因此不会发生所描述的问题。如果一个操作失败,执行该操作的线程将减少一个引用计数(当线程接收到套接字时该引用计数先前已增加),如果该计数变为零,则关闭套接字。显然,这个引用计数是在程序中实现的,而不是操作系统引用计数。
    • 你甚至不需要担心引用计数,如果读取操作失败,只需关闭套接字。
    • @TrayMan - OP 说部分问题是挂起的操作需要在连接断开之前完成。您的解决方案不完整,因为它没有解决这部分问题。
    • 我不是这么读的。但要做到这一点,您只需从我的解决方案中删除关闭部分。然后在套接字上运行的最后一个线程将关闭它。
    【解决方案5】:

    好的,找到答案了。

    这里最好的方法是调用 accept() 并获得可用的最低 fd,用你知道的数字复制它,如 dup2(6,1000) 和 close(6),你现在可以控制你的 fd 范围使用。

    下一次接受将再次出现 6 或类似的,我们将 dup2(6,999);并继续减少,如果它变得太低,则将其重置。

    由于接受总是在同一个线程中完成,并且 dup2 和 close 与总是在那里完成的接受相比并不昂贵,因此非常适合我的需求。

    【讨论】:

      【解决方案6】:

      我仍然会小心将 dup2() 用于众所周知的 fd 值。请记住, dup2() 将在复制之前对目标执行关闭;如果您开始打开 ​​1000 个文件,这可能会与执行无关 I/O 的一些无关线程发生冲突。

      如果我是你,考虑到你坚持的约束,我会在互斥体中使用 dup()(而不是 dup2())。 (如果你担心的话,可能是 per-fd 互斥锁。)

      【讨论】:

      • 事情是接受 & dup2 并且所有将始终在同一个线程上执行。所以我可以从 MAX_FD 到 10 左右然后重新开始,当 MAX_FD 刚刚关闭时 dup2() 将 fd 设置为 MAX_FD 的可能性,因为我们重新开始,并没有真正考虑到。
      • 但是你能证明你没有打开无关文件吗?或者,也许某个图书馆正在代表您做这些事情?你能证明自然产生的 FD 的数量最终不会达到 1000 个吗?也许你可以保证这些事情,但这种事情会让我对你的做法感到紧张。
      猜你喜欢
      • 1970-01-01
      • 2017-08-15
      • 2017-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-02
      • 2019-03-06
      相关资源
      最近更新 更多