你在项目里真正会遇到的,通常不是"请背一下三次握手",而是这些现象:
- 某次发布后接口突然大量超时,但服务端 CPU 并不高
- 机器上
TIME_WAIT一堆,端口被耗尽,新连接建不起来 - 偶发性连接建立慢,抓包发现有
SYN重传 - 连接断开时出现半关闭,客户端读到 EOF,但服务端还在写
这些问题背后都指向同一件事:TCP 连接的建立与关闭到底在确认什么,为什么要这么做。
这篇我用"知识分享 + 可验证"的方式讲清楚:
- 三次握手:怎么把"连接建立"做成闭环
- 四次挥手:为什么经常不是一次就能关掉
- TIME_WAIT:它不是 bug,但会在高并发短连接下变成问题
目录(按排查顺序)
-
- 先把目标说清:握手/挥手分别要解决什么问题
-
- 三次握手:每一步到底在确认什么
-
- 两次握手为什么不行:服务端误判与旧报文
-
- 四次挥手:全双工 + 半关闭
-
- TIME_WAIT:为什么要等、为什么是 2MSL、什么时候会爆
-
- 实战:抓包/命令怎么看握手、重传、TIME_WAIT
-
- 最后总结
1. 先把目标说清:握手/挥手分别要解决什么问题
先不要急着背 SYN/ACK/FIN,你先记住两个目标:
- 握手(建立连接):双方要确认"连接真的建立好了",避免一方误判导致资源浪费或历史报文干扰。
- 挥手(断开连接):双方要确认"我不再发送了",并且把剩余数据尽可能传完,因为 TCP 是全双工。
你可以用一个生活类比来理解:
- 握手像"双方确认能不能正常通话":你听得到我吗?我听得到你吗?我们从哪个编号开始对齐?
- 挥手像"双方结束通话":我这边先不说了(但我可能还在听你说完),等你也说完,我们再彻底挂断。
2. 三次握手:每一步到底在确认什么
先给出流程图(你要能看懂抓包里这 3 行在干什么):
text
1) 客户端 -> 服务端:SYN = 1, seq = x
2) 服务端 -> 客户端:SYN = 1, ACK = 1, seq = y, ack = x + 1
3) 客户端 -> 服务端:ACK = 1, ack = y + 1
接下来我们用"每一步到底在确认什么"的方式去理解它。
第 1 次:客户端发 SYN(我想建立连接)
- 我想建立连接
- 我把我的初始序列号
x告诉你
第 2 次:服务端回 SYN+ACK(我收到了,并把我的信息也告诉你)
- 我收到了你的请求(ACK = x + 1)
- 我也想建立连接(SYN = 1)
- 我把我的初始序列号
y告诉你
第 3 次:客户端回 ACK(我也收到了,你可以放心进入连接态)
- 我收到了你的
y - 我确认你也能收能发
到这一步,双方都确认:
- 对方收得到
- 对方发得出
- 序列号已经同步
3. 两次握手为什么不行(核心是避免"服务端单方面误判")
这里不要死背结论,你要抓住关键风险:
- 两次握手时,服务端无法确认"客户端是否收到了服务端的应答"。
如果服务端过早认为"连接已建立",就可能出现:
- 资源被占用(连接状态、缓存、队列等)
- 被历史/延迟的 SYN 干扰
如果只有两次:
- 客户端发 SYN
- 服务端回 SYN+ACK
这时服务端以为连接建立了,但客户端如果因为网络问题没收到第二步,客户端会重发 SYN。
服务端可能会:
- 误以为是新连接
- 重复分配资源
更经典也更好理解的解释是"旧 SYN 报文"的问题(延迟到达):
- 客户端发起过一次连接请求(SYN)
- 由于网络拥塞,这个 SYN 迟到了
- 客户端早就超时放弃,并重新建立了新连接
- 旧 SYN 这时到达服务端
如果只有两次握手,服务端可能直接认为连接成立并占用资源。
所以第三次握手的本质是:
- 让客户端对服务端的
SYN+ACK做一次确认 - 把"连接建立"做成一个闭环
这样服务端才不会单方面误判。
4. 四次挥手:全双工导致的"分两次关门"
四次挥手背诵版:
text
1) 主动关闭方 -> 被动关闭方:FIN
2) 被动关闭方 -> 主动关闭方:ACK
3) 被动关闭方 -> 主动关闭方:FIN
4) 主动关闭方 -> 被动关闭方:ACK
为什么不是 3 次?为什么通常要 4 次?
关键在于:
- TCP 是全双工
- 一方说"我不发了"(FIN)不代表另一方也立刻"我也不发了"
也就是说,关闭是双向的:
- A 关闭 A->B 的发送通道
- B 关闭 B->A 的发送通道
所以经常会出现"半关闭(half-close)":
- A 不发了,但还可以接收 B 发来的数据
这也是为什么挥手通常是 4 次。
5. TIME_WAIT:为什么要等、为什么是 2MSL
这是挥手后的高频追问。
5.1 为什么会有 TIME_WAIT
主动关闭方在最后一次 ACK 后会进入 TIME_WAIT。
目的主要有两个:
-
确保最后的 ACK 能到达:如果最后 ACK 丢了,被动方会重发 FIN,主动方还能在 TIME_WAIT 期间重发 ACK。
-
让旧连接的报文在网络中自然消失:避免旧报文混入新连接。
5.2 为什么是 2MSL
MSL 是报文在网络中的最大生存时间。
- 等待 2 倍 MSL ,可以保证:
- 你发出的最后 ACK 能走到对方
- 对方可能重发的 FIN 也能走回来
形成一个"最坏情况下的来回"。
5.3 TIME_WAIT 太多怎么办(先理解原因,再谈处理)
先记住一个学习原则:
- TIME_WAIT 多往往不是"网络坏了",而是你的业务产生了大量短连接。
所以排查/处理时更推荐:
- 先从业务与架构入手:是否可以复用连接(如 HTTP keep-alive、连接池、长连接)
- 再谨慎考虑系统参数调整(如果你还没理解参数含义,尽量别乱改)
6. 实战:抓包/命令怎么看握手、重传、TIME_WAIT
6.1 Windows 快速看 TIME_WAIT 是否异常
bash
netstat -ano | findstr TIME_WAIT
你需要关注:
- 是不是集中在某个服务端口/某个进程 PID
- 是不是在某个时间段突然飙升(通常对应流量突增或短连接模型)
6.2 抓包怎么确认"连接建立慢"是不是 SYN 重传
用 Wireshark 过滤:
tcp.flags.syn == 1 && tcp.flags.ack == 0(只看 SYN)
如果你看到同一个四元组反复发 SYN,说明:
- 客户端在重试建连(可能是网络丢包,也可能是服务端没及时响应)
6.3 看到 FIN/ACK 怎么判断是谁在主动关闭
- 谁先发
FIN,谁就是主动关闭方 - 主动关闭方通常会进入
TIME_WAIT
7. 最后总结
7.1 你应该记住的 3 句话
- 三次握手的核心是把"连接建立"做成确认闭环,避免服务端单方面误判。
- 四次挥手的核心是全双工要分别关闭两个方向的发送通道,因此会出现半关闭。
- TIME_WAIT 是连接收尾机制,在短连接/高并发下可能变成容量问题,要优先从连接复用入手。