【问题标题】:CLOSED error when establishing lots of connections with gen_tcp in parallel (Bug?)与 gen_tcp 并行建立大量连接时出现 CLOSED 错误(错误?)
【发布时间】:2016-10-21 11:59:20
【问题描述】:

在尝试并行建立大量 TCP 连接时,我观察到一些奇怪的行为,我认为gen_tcp 中存在潜在错误。

该场景是一个服务器在一个具有多个并发接受器的端口上侦听。从客户端我通过调用gen_tcp:connect/3 建立连接,然后我向服务器发送“Ping”消息并在被动模式下等待“Pong”响应。按顺序执行 'get_tcp:connect/3' 调用时一切正常,包括大量连接(我测试了高达 ~ 28000)。

在尝试建立大量并行连接时会出现问题(取决于大约 75 到数百台机器)。虽然大多数连接仍然建立,但一些连接失败并在gen_tcp:recv/3 中出现closed 错误。奇怪的是,这些连接之前没有失败,对gen_tcp:connect/3gen_tcp:send/2 的调用都成功了 (即返回ok)。在服务器端,我没有看到这些“奇怪”连接的匹配连接,即没有返回 gen_tcp:accept/1。我的理解是,成功的 'get_tcp:connect/3' 应该会在服务器端产生匹配的接受连接。

我已经提交了bug report,在那里你可以找到更详细的描述和一个最小的代码示例来演示这个问题。我能够在 Linux 和 Mac OS X 以及不同的 Erlang 版本上重现该问题。

我的问题是:

  1. 是否有人能够重现该问题并确认这是错误行为?
  2. 有解决方法的想法吗?如何处理这个问题,其他顺序启动所有连接(这需要永远)?

【问题讨论】:

  • 不看示例,我首先想到的是 tcp backlog 的大小可能太小了。你试过提升它吗? (erlang.org/doc/man/gen_tcp.html#listen-2) 请参阅 veithen.github.io/2014/01/01/… 了解它的作用。
  • 我同意@johlo。我查看了您的代码,但您没有使用 {backlog, B} 选项。 Erlang TCP sockets get closed 的可能重复项
  • @johlo 谢谢,很棒的提示和很棒的链接。增加 backlog 解决了这个问题,文章解释了为什么对 gen_tcp:connect/3 的调用没有错误返回(因为它从服务器获得了 SYN/ACK 并发送了最终的 ACK),但服务器最终没有处于建立状态(因为它忽略了最后的 ACK)。
  • 我根据@johlo 的建议开始回答这个问题。随时帮助改进答案。我仍然有点不清楚为什么仅在客户端尝试接收而不是发送时才发现问题。这是否特定于发送和接收的工作方式?或者这是由于时间问题(尝试发送时服务器仍在重试SYN-ACKs)?

标签: sockets erlang gen-tcp


【解决方案1】:

TCP 3 次握手 客户端服务器

  connect()│──┐          │listen()
           │  └──┐       │
           │      SYN    │
           │        └──┐ │
           │           └▶│   STATE
           │          ┌──│SYN-RECEIVED
           │       ┌──┘  │
           │   SYN-ACK   │
           │ ┌──┘        │
   STATE   │◀┘           │
ESTABLISHED│──┐          │
           │  └──┐       │
           │     └ACK    │
           │        └──┐ │   STATE
           │           └▶│ESTABLISHED
           ▽             ▽

问题在于用于建立 TCP 连接的 3 次握手和侦听套接字上的传入连接队列的更精细细节。详情请见excellent article,以下大部分解释均来自本文。

在 Linux 中实际上有两个用于传入连接的队列。当服务器接收到连接请求(SYN 数据包)并转换到状态SYN-RECEIVED 时,此连接被放入SYN 队列中。如果收到相应的ACK,则将连接放入接受队列以供应用程序使用。 gen_tcp:listen/2{backlog, N}(默认值:5)选项决定了访问队列的长度。

当服务器收到ACK 而接受队列已满时,ACK 基本上会被忽略,并且不会向客户端发送RST。与SYN-RECEIVED 状态相关的超时:如果没有收到ACK(或忽略,就像这里的情况),服务器将重新发送SYN-ACK。然后客户端重新发送ACK。如果应用程序在达到SYN-ACK 重试的最大次数之前从接受队列中消费了一个条目,则服务器最终将处理重复的ACKs 之一并转换到状态ESTABLISHED。如果已达到最大重试次数,服务器将向客户端发送RST 以重置连接。

回到并行启动大量连接时观察到的行为。解释是,服务器上的接受队列填满的速度比我们的应用程序消耗接受的连接的速度要快。一旦收到第一个SYN-ACK,客户端上的gen_tcp:connect/3 调用就会成功返回。连接不会立即重置,因为服务器会重试SYN-ACK。服务器不会将这些连接报告为成功,因为它们仍处于状态 SYN-RECEIVED

在 BSD 派生系统(包括 Mac OS X)上,传入连接队列的工作方式略有不同,请参阅上面提到的 article

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-03
    • 2021-10-20
    • 2011-12-22
    相关资源
    最近更新 更多