1

在尝试并行建立大量 TCP 连接时,我观察到一些奇怪的行为,我认为这是gen_tcp.

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

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

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

我的问题是:

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

1 回答 1

3

TCP 3 次握手客户端服务器

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

问题在于用于建立 TCP 连接的 3 次握手的更详细的细节以及侦听套接字处传入连接的队列。有关详细信息,请参阅这篇优秀的文章,以下大部分解释都是由本文提供的。

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

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

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

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

于 2016-06-21T13:42:49.443 回答