搞网络和并发编程的人,大概率都被面试官按着头背过TCP的三次握手和四次挥手。这个词我当年也背过,但真正吃透是在上了几年生产环境之后——当你面对一堆TIME_WAIT、CLOSE_WAIT,看着服务端端口被占满、客户端连接被重置时,才会明白这些状态机图居然是活生生会咬人的。TCP连接管理,说白了就是三次握手建连、四次挥手断连、保活机制兜底这三件事,但每一件背后都藏着一堆真实事故和排障套路。这篇文章我会结合抓包报文和实际排障经验,把TCP连接管理到底在干什么、以及遇到问题时怎么查,一次性讲透。
1. 为什么面试官总爱揪着三次握手不放
1.1 从一次真实的连接建立说起
先别急着背那三行字,打开Wireshark抓一次包看看TCP连接建立时到底发生了什么。最简单的方式是用nc或者一个Python的socket连接,只要连接建立成功,抓包里就能看到三个报文:
bash复制nc -vz 192.168.1.100 8080
在Wireshark里过滤tcp.port == 8080,你会看到这样的序列:
- 客户端 → 服务端:
SYN,seq = 0(相对序号) - 服务端 → 客户端:
SYN + ACK,seq = 0,ack = 1 - 客户端 → 服务端:
ACK,ack = 1
注意看序号的变化。第一次握手发送SYN时,客户端消耗掉一个序号,所以第二次的确认号是1;第二次SYN+ACK同样消耗掉一个序号,所以第三次的确认号也是1。这个"SYN占一个序列号"的细节很多人忽略,但它恰恰是理解整个握手过程的基础。后面看抓包时如果发现ACK号奇怪地递增了,多半就是SYN/FIN这种控制标志占用了序号。
第一次握手是客户端说"我要连你",第二次是服务端说"我听到了,同时我也要连你",第三次是客户端说"我也收到了"。到这里双方才真正建立互信,连接进入ESTABLISHED状态。在Linux下,你可以在客户端和服务端分别用ss -tn state established来验证连接状态。这里有个易错点:第三次握手之前,服务端已经从LISTEN进入SYN_RCVD状态,但此时连接对应用层还不可见;直到第三次ACK到达,服务端的accept才会返回。
1.2 三次握手的本质:两个方向上的确认
为什么非得是三次,不能是两次?核心原因是TCP是全双工协议,数据可以同时双向传输,所以连接建立必须确认两个方向都通。
拿打电话类比最直观:
- A说:"喂,你听得到吗?"(SYN)
- B说:"听得到,你能听到我吗?"(SYN+ACK)
- A说:"听到了。"(ACK)
三次对话后,A确认了自己说的话B能听到、B说的话自己也能听到;B确认了自己说的话A能听到、A说的话自己也能听到。两个方向的收、发链路全部验证了一遍。如果是两次握手,B说完"听得到"就直接认为连接建立了,但B根本不知道A有没有听到这句话;万一这句话丢了,A完全不知情,B却在那里傻等,两边状态就不一致了。
这个"双向确认"的思路在排查连接问题时很有用。比如你发现客户端connect成功但服务端没有反应,往往是第三次握手没到达服务端,或者中间设备把ACK丢了。这种问题抓包时很清晰:客户端发出ACK后停在ESTABLISHED,服务端还停在SYN_RCVD,两边的状态不对称,数据自然传不动。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三次握手的细节:不只是“你好,你好,好的”
2.1 三次握手流程拆解与报文分析
从抓包的角度,三次握手的每个报文都有几个关键字段要盯死:Seq号、Ack号、Flags标志位。
第一次握手:
- 客户端 → 服务端
- Flags:
SYN - Seq:
0(实际是随机初始序号ISN,抓包工具会显示相对值) - 这个SYN包里还带着MSS、窗口大小等选项,用来协商最大报文长度和接收窗口
第二次握手:
- 服务端 → 客户端
- Flags:
SYN + ACK - Seq:
0(服务端自己的ISN) - Ack:
1(客户端ISN + 1) - 服务端在这个包里同样带上自己的MSS和窗口选项
第三次握手:
- 客户端 → 服务端
- Flags:
ACK - Ack:
1(服务端ISN + 1) - 此时客户端可以携带数据,第三次握手和第一个数据包在抓包里往往是同一帧
真正生产环境里,你很少看到客户端主动connect超时的,反而是服务端半连接队列被塞满的情况更多。半连接队列(SYN Queue)是内核保存SYN_RCVD状态连接的地方,如果这个队列满了,新来的SYN会被直接丢弃。所以你会看到客户端connect卡住,抓包时却一直能看到SYN重传,这就是服务端压根没把SYN收进去。
常见的查看半连接队列溢出的命令:
bash复制netstat -s | grep -i 'listen queue'
netstat -s | grep -i 'SYN'
如果输出里有SYNs to LISTEN sockets dropped或者listen queue overflow,基本可以确认是半连接队列被打满了。
2.2 为什么不是两次或四次
除了上面对话层面的解释,三次握手还有一个隐蔽
