当尝试并行建立大量TCP连接时,我发现了一些我认为是gen_tcp
中潜在错误的奇怪行为。
该方案是监听具有多个并发接受器的端口的服务器。从客户端我通过调用gen_tcp:connect/3
建立连接,之后我向服务器发送“Ping”消息并在被动模式下等待“Pong”响应。当按顺序执行'get_tcp:connect / 3'调用时,所有工作都正常,包括大量连接(我测试到最多~28000)。
尝试并行建立大量连接时会出现问题(取决于~75和几百之间的机器)。虽然大多数连接仍然已建立,但某些连接因closed
中的gen_tcp:recv/3
错误而失败。奇怪的是,这些连接之前没有失败,对gen_tcp:connect/3
和gen_tcp:send/2
的调用都是成功的
(即返回ok
)。在服务器端,我没有看到这些“怪异”连接的匹配连接,即没有返回gen_tcp:accept/1
。我的理解是,成功的'get_tcp:connect / 3'应该在服务器端产生匹配的接受连接。
我已经提交了bug report,您可以在那里找到更详细的说明和最小的代码示例来演示此问题。我能够在Linux和Mac OS X以及不同的Erlang版本上重现这个问题。
我的问题是:
答案 0 :(得分:3)
TCP 3次握手 客户端服务器
connect()│──┐ │listen()
│ └──┐ │
│ SYN │
│ └──┐ │
│ └▶│ STATE
│ ┌──│SYN-RECEIVED
│ ┌──┘ │
│ SYN-ACK │
│ ┌──┘ │
STATE │◀┘ │
ESTABLISHED│──┐ │
│ └──┐ │
│ └ACK │
│ └──┐ │ STATE
│ └▶│ESTABLISHED
▽ ▽
问题在于建立TCP连接的三次握手的详细细节以及侦听套接字上的传入连接的队列。有关详细信息,请参阅此excellent article,本文将介绍以下大部分说明。
在Linux中,实际上有两个队列用于传入连接。当服务器收到连接请求(SYN
数据包)并转换到状态SYN-RECEIVED
时,此连接将放在SYN
队列中。如果收到相应的ACK
,则将连接放在接受队列中以供应用程序使用。 {backlog, N}
的{{1}}(默认值:5)选项决定了访问队列的长度。
当服务器在接受队列已满时收到gen_tcp:listen/2
时,ACK
基本上被忽略,并且没有ACK
被发送到客户端。存在与RST
状态关联的超时:如果没有收到SYN-RECEIVED
(或忽略,如此处所示),则服务器将重新发送ACK
。然后,客户端重新发送SYN-ACK
。如果应用程序在达到最大ACK
次重试次数之前使用接受队列中的条目,则服务器最终将处理重复SYN-ACK
之一并转换为状态ACKs
。如果已达到最大重试次数,服务器将向客户端发送ESTABLISHED
以重置连接。
回到并行启动大量连接时观察到的行为。解释是,服务器上的接受队列填充速度比我们的应用程序消耗接受的连接要快。一旦收到第一个RST
,客户端的gen_tcp:connect/3
次呼叫就会成功返回。由于服务器重试SYN-ACK
,因此不会立即重置连接。服务器不会将这些连接报告为成功,因为它们仍处于状态SYN-ACK
。
在BSD派生系统(包括Mac OS X)上,传入连接的队列有点不同,请参阅上面提到的article。