与 gen_tcp 并行建立大量连接时出现 CLOSED 错误(错误?)
CLOSED error when establishing lots of connections with gen_tcp in parallel (Bug?)
在尝试并行建立大量 TCP 连接时,我观察到一些奇怪的行为,我认为这是 gen_tcp
中的一个潜在错误。
场景是服务器监听一个端口,有多个并发接受器。从客户端我通过调用 gen_tcp:connect/3
建立连接,然后我向服务器发送 "Ping" 消息并在被动模式下等待 "Pong" 响应。当按顺序执行 'get_tcp:connect/3' 调用时,一切正常,包括大量连接(我测试了 ~ 28000)。
当尝试建立大量并行连接时会出现此问题(取决于机器在 ~75 到几百之间)。虽然大多数连接仍然建立,但一些连接失败并在 gen_tcp:recv/3
中出现 closed
错误。奇怪的是,这些连接之前并没有失败,对 gen_tcp:connect/3
和 gen_tcp:send/2
的调用都成功了
(即返回 ok
)。在服务器端,我没有看到这些 "weird" 连接的匹配连接,即没有返回 gen_tcp:accept/1
。据我了解,成功的 'get_tcp:connect/3' 应该会在服务器端产生匹配的已接受连接。
我已经提交了 bug report,您可以在其中找到更详细的描述和最简单的代码示例来演示问题。我能够在 Linux 和 Mac OS X 以及不同的 Erlang 版本上重现该问题。
我的问题是:
- 是否有人能够重现该问题并确认这是错误行为?
- 有什么解决方法吗?如何处理这个问题,其他按顺序启动所有连接(这需要永远)?
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
以重置连接。
回到并行启动大量连接时观察到的行为。解释是,服务器上的接受队列比我们的应用程序消耗接受的连接更快地填满。 gen_tcp:connect/3
在客户端 return 收到第一个 SYN-ACK
后立即成功调用。连接不会立即重置,因为服务器重试 SYN-ACK
。服务器不会将这些连接报告为成功,因为它们仍处于状态 SYN-RECEIVED
.
在 BSD 派生系统(包括 Mac OS X)上,传入连接的队列有点不同,请参见上面提到的 article。
在尝试并行建立大量 TCP 连接时,我观察到一些奇怪的行为,我认为这是 gen_tcp
中的一个潜在错误。
场景是服务器监听一个端口,有多个并发接受器。从客户端我通过调用 gen_tcp:connect/3
建立连接,然后我向服务器发送 "Ping" 消息并在被动模式下等待 "Pong" 响应。当按顺序执行 'get_tcp:connect/3' 调用时,一切正常,包括大量连接(我测试了 ~ 28000)。
当尝试建立大量并行连接时会出现此问题(取决于机器在 ~75 到几百之间)。虽然大多数连接仍然建立,但一些连接失败并在 gen_tcp:recv/3
中出现 closed
错误。奇怪的是,这些连接之前并没有失败,对 gen_tcp:connect/3
和 gen_tcp:send/2
的调用都成功了
(即返回 ok
)。在服务器端,我没有看到这些 "weird" 连接的匹配连接,即没有返回 gen_tcp:accept/1
。据我了解,成功的 'get_tcp:connect/3' 应该会在服务器端产生匹配的已接受连接。
我已经提交了 bug report,您可以在其中找到更详细的描述和最简单的代码示例来演示问题。我能够在 Linux 和 Mac OS X 以及不同的 Erlang 版本上重现该问题。
我的问题是:
- 是否有人能够重现该问题并确认这是错误行为?
- 有什么解决方法吗?如何处理这个问题,其他按顺序启动所有连接(这需要永远)?
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
以重置连接。
回到并行启动大量连接时观察到的行为。解释是,服务器上的接受队列比我们的应用程序消耗接受的连接更快地填满。 gen_tcp:connect/3
在客户端 return 收到第一个 SYN-ACK
后立即成功调用。连接不会立即重置,因为服务器重试 SYN-ACK
。服务器不会将这些连接报告为成功,因为它们仍处于状态 SYN-RECEIVED
.
在 BSD 派生系统(包括 Mac OS X)上,传入连接的队列有点不同,请参见上面提到的 article。