半建立的 TCP 连接

Posted

技术标签:

【中文标题】半建立的 TCP 连接【英文标题】:Half-Established TCP Connections 【发布时间】:2016-10-26 03:31:59 【问题描述】:

半建立连接

对于半建立的连接,我的意思是客户端对connect() 的调用成功返回,但服务器对accept() 的调用没有成功。这可以通过以下方式发生:客户端调用connect(),从而将SYN 数据包发送到服务器。服务器进入状态SYN-RECEIVED 并向客户端发送SYN-ACK 数据包。这会导致客户端回复ACK,进入状态ESTABLISHED 并从connect() 调用返回。如果最终的ACK 丢失(或被忽略,由于服务器上的接受队列已满,这可能是更可能的情况),服务器仍处于状态SYN-RECEIVED 并且accept() 不会返回。由于与SYN-RECEIVED 状态相关的超时,SYN-ACK 将被重新发送,允许客户端重新发送ACK。如果服务器最终能够处理ACK,它也会进入状态ESTABLISHED。否则它将最终重置连接(即向客户端发送RST)。

您可以通过在单个侦听套接字上启动 很多 个连接来创建此场景(如果您不调整积压和tcp_max_syn_backlog)。有关详细信息,请参阅 this questions 和 this article。

实验

我进行了几次实验(this code 的变体)并观察到一些我无法解释的行为。所有实验都是使用 Erlang 的 gen_tcp 和当前的 Linux 进行的,但我强烈怀疑答案并非特定于此设置,因此我试图在此处保持更通用。

connect() -> 等待 -> send() -> receive()

我的出发点是从客户端建立连接,等待 1 到 5 秒,向服务器发送“Ping”消息并等待回复。通过这个设置,我观察到receive() 失败并出现错误closed 当我有一个半建立的连接时。在半建立的连接上send() 期间从未出现错误。您可以在here 找到有关此设置的更详细说明。

connect() -> 漫长的等待 -> send()

为了查看,如果我在半建立的连接上发送数据时出现错误,我在发送数据前等待了 4 分钟。这 4 分钟应涵盖与半建立连接相关的所有超时和重试。仍然可以发送数据,即 send() 返回没有错误。

connect() -> receive()

接下来我测试了如果我只用很长的超时时间(5 分钟)调用receive() 会发生什么。我的期望是得到一个closed 错误的半建立连接,就像在原始实验中一样。唉,什么都没有发生,没有抛出任何错误,最终接收超时。

我的问题

    我所说的半建立连接是否有一个通用名称? 为什么在半建立的连接上send() 会成功? 为什么 receive() 只有在我先发送数据时才会失败?

欢迎提供任何帮助,尤其是详细说明的链接。

【问题讨论】:

【参考方案1】:

    从客户端的角度来看,会话已经完全建立,它发送 SYN,返回 SYN/ACK 并发送 ACK。只有在服务器端,您才处于半建立状态。 (即使它从服务器收到重复的 SYN/ACK,它也会重新 ACK,因为它处于建立状态。)

    此会话上的send 工作正常,因为就客户端而言,会话已建立。发送的数据不必由远端确认才能成功(当数据复制到内核缓冲区时,发送系统调用完成),但请参见下文。

    我相信这里发送实际上正在在连接上产生错误(可能是 RST),因为接收系统无法在它尚未完成建立的会话上确认数据。我的猜测是,在发送后发生的引用客户端套接字的 任何 系统调用加上短暂的延迟(即当 RST 有机会返回时)将导致错误。

    接收本身永远不会导致错误,因为客户端不需要为接收做任何事情(我的意思是 TCP 协议);它只是在等待。但是一旦你发送了一些数据,你就强迫了服务器端:它要么完成了会话建立(在这种情况下它可以接受数据),要么它必须发送一个重置(我猜这里它不能“保持” " 未完全建立的会话中未传递的数据)。

【讨论】:

关于 2. 和 3.:您可能会看到类似 connect() -> long wait -> send() -> wait -> send() 这样的错误? 是的。我会这么认为。确切的行为可能确实取决于运行服务器端的操作系统。 确实,connect() -> 等待 -> send() -> 短暂等待 -> send() 在第二个 send() 上失败!

以上是关于半建立的 TCP 连接的主要内容,如果未能解决你的问题,请参考以下文章

TCP 连接三次握手、四次挥手

TCP半关闭

TCP长时间IDLE操作系统会清除连接么

SYN攻击解析

TCP半连接对端不断开,试试用RST

TCP