连接超时原理:TCP三次握手与超时重传

说到连接超时,咱们得先搞清楚一个基础问题——TCP连接是怎么建起来的。说白了,就是那个经典的三次握手。我干了这么多年网络排障,发现很多人对三次握手停留在背口诀的阶段,真到抓包分析时就懵了。今天咱们把它彻底掰开揉碎。

三次握手:连接建立的基石

TCP是面向连接的协议,通信前必须先建立连接。这个建立过程就是三次握手。你想想看,就像两个人打电话,得先确认对方在听,对吧?

核心要点:三次握手的本质是双方确认彼此的收发能力都正常。

具体流程是这样的:

  1. 客户端发送SYN包——客户端说:“我要跟你建立连接,这是我的初始序列号x。”
  2. 服务端回复SYN-ACK包——服务端说:“收到你的请求,这是我的初始序列号y,我确认收到了你的x。”
  3. 客户端发送ACK包——客户端说:“我确认收到了你的y,连接建立完成。”

嗯,这里要注意:第三步的ACK包其实可以携带数据。但在实际抓包中,大部分场景第三步就是个纯ACK,不带数据。

SYN包:连接请求的发起者

SYN包是三次握手的第一个包。它的标志位SYN=1,序列号是一个随机生成的初始值(ISN)。

我记得有一次排查一个跨机房连接超时的问题,抓包发现客户端一直在发SYN,但服务端根本没回应。后来发现是中间防火墙把SYN包给丢了。这种问题,光看应用层日志根本看不出来,必须抓包。

实战技巧:抓包时看到大量重传的SYN包,但没有任何SYN-ACK回应,大概率是中间网络设备(防火墙、路由器)把包丢了,或者服务端根本没在监听这个端口。

SYN-ACK包:服务端的回应

服务端收到SYN后,如果端口在监听,就会回复SYN-ACK包。这个包同时设置了SYN和ACK标志位,ACK号是客户端的ISN+1,表示“我收到了你的SYN”。

这里有个坑——SYN-ACK包丢了怎么办?客户端收不到SYN-ACK,就会认为SYN包丢了,然后触发超时重传。我曾经遇到过一个案例,服务端CPU负载过高,导致SYN-ACK包在协议栈里排队太久,客户端那边已经超时重传了。抓包一看,客户端发了3个SYN,服务端回了3个SYN-ACK,但客户端只认第一个,后面的都当重复包丢弃了。

避坑指南:我曾经被一个假象坑过——抓包看到服务端确实回了SYN-ACK,但客户端就是没收到。后来发现是服务端的SYN-ACK包被出口路由器的ACL给拦截了。所以抓包一定要在两端都抓,才能定位问题到底出在哪一侧。

ACK包:握手的收尾

客户端收到SYN-ACK后,发送ACK包确认。这个包的ACK号是服务端的ISN+1。至此,连接建立完成,双方可以开始传输数据了。

你可能会问:为什么是三次,不是两次?我个人的理解是:两次握手无法防止已失效的连接请求到达服务端。举个例子,客户端发了个SYN,网络延迟导致这个包很久才到服务端,但客户端已经超时重传了。如果只有两次握手,服务端收到这个延迟的SYN就会建立连接,白白浪费资源。三次握手通过客户端的最终ACK来确认,就能避免这个问题。

超时重传机制:TCP的可靠性保障

TCP是可靠传输协议,怎么保证可靠?其中一个关键机制就是超时重传。发送方发出一个包后,会启动一个定时器。如果在规定时间内没收到确认,就重新发送这个包。

这个“规定时间”不是固定的。TCP会根据网络状况动态调整——这就是RTO(Retransmission Timeout)。RTO的计算基于RTT(Round-Trip Time),也就是一个包从发出去到收到确认的时间。

参数 说明 默认值(Linux)
RTO_min 最小超时时间 200ms
RTO_max 最大超时时间 120s
初始RTO 第一次发送SYN时的超时时间 1s(实际由内核决定)
重传次数 SYN包最大重传次数 6次(约127s后放弃)

我个人习惯在排查连接超时问题时,先看抓包里的重传间隔。如果重传间隔是1s、2s、4s、8s这样指数增长,说明是正常的TCP超时重传。如果重传间隔固定不变,那可能是应用层自己实现的超时逻辑,问题可能出在代码里。

连接超时的几种典型场景

结合三次握手和超时重传,连接超时通常出现在以下几个环节:

  • SYN包发不出去——客户端路由不可达,或者防火墙拦截了出站SYN。抓包特征:客户端一直在重传SYN,但抓不到任何回应。
  • SYN-ACK包回不来——服务端回了SYN-ACK,但被中间设备拦截了。抓包特征:客户端重传SYN,服务端也在重传SYN-ACK,两边都在重传。
  • ACK包丢失——客户端发了ACK,但服务端没收到。抓包特征:服务端重传SYN-ACK,客户端回复ACK,反复循环。
  • 服务端端口未监听——服务端收到SYN后直接回RST包。抓包特征:客户端发SYN,服务端立即回RST,连接立即失败。

关键判断:抓包时看到RST包,说明服务端是活着的,但端口没开或者应用没在监听。看到重传的SYN但没有SYN-ACK,说明服务端可能挂了或者网络不通。看到重传的SYN-ACK,说明客户端可能没收到或者收到了但ACK丢了。

知识体系总览

下面这张图把三次握手和超时重传的核心逻辑串起来了。我建议你把它存下来,以后排查连接超时问题时对照着看。

TCP三次握手与超时重传流程 客户端 服务端 CLOSED → SYN_SENT LISTEN → SYN_RCVD ① SYN (seq=x) 若未收到SYN-ACK,超时重传 ② SYN-ACK (seq=y, ack=x+1) ③ ACK (ack=y+1) ESTABLISHED ESTABLISHED 超时重传机制 • 初始RTO:约1秒(Linux内核决定) • 重传策略:指数退避(1s → 2s → 4s → 8s → 16s → 32s) • 最大重传次数:6次(约127秒后放弃,返回连接超时) • 抓包特征:连续多个SYN包,间隔逐渐增大,无SYN-ACK回应 • 常见原因:防火墙拦截、路由不可达、服务端未监听端口

这张图把三次握手的三个步骤和超时重传的机制放在一起了。你排查连接超时问题时,对照这个流程,看抓包卡在哪一步,问题就定位到哪一步。

我的习惯:拿到一个连接超时的抓包,我第一件事就是看有没有SYN包。没有SYN?那客户端根本没发起连接。有SYN但没SYN-ACK?看重传间隔,判断是网络问题还是服务端问题。有SYN-ACK但没最后的ACK?看看是不是客户端收到SYN-ACK后丢了或者被防火墙拦截了。三步走完还没建立连接?那可能是应用层的问题了。