TCP三次握手与四次挥手:Wireshark视角下的连接建立与断开
TCP的连接管理,说白了就是两台机器之间怎么「打招呼」和「说再见」。我见过不少刚入行的同事,背得滚瓜烂熟——SYN、SYN-ACK、ACK,但一打开Wireshark就懵了。嗯,今天我们就从抓包的角度,把这两个过程彻底讲透。
三次握手:建立连接的「三句话」
先看一个正常的TCP连接建立过程。客户端主动发起连接,服务端被动等待。整个过程就三条报文,一条不多,一条不少。
核心要点:三次握手的本质是「双方确认彼此的收发能力」。客户端能发,服务端能收;服务端能发,客户端能收——双向通道才算打通。
握手流程拆解
- 第一次握手(SYN):客户端发送SYN包,seq=x。此时客户端状态变为SYN_SENT。
- 第二次握手(SYN+ACK):服务端回复SYN+ACK,seq=y,ack=x+1。服务端状态变为SYN_RCVD。
- 第三次握手(ACK):客户端发送ACK,seq=x+1,ack=y+1。双方进入ESTABLISHED状态。
我在项目中遇到过一个问题:某台服务器频繁出现连接超时,抓包一看,三次握手只完成了两次,第三次ACK丢了。服务端一直在等那个ACK,客户端以为连接已经建好了——这就是典型的「半连接」问题。
Wireshark过滤技巧
想快速定位握手报文?用这个过滤表达式:
tcp.flags.syn == 1 and tcp.flags.ack == 0 // 只看纯SYN包
tcp.flags.syn == 1 and tcp.flags.ack == 1 // 只看SYN+ACK包
tcp.flags.syn == 0 and tcp.flags.ack == 1 // 只看纯ACK包
我个人习惯在Wireshark里加个颜色规则:SYN包标成浅蓝色,SYN+ACK标成浅绿色,一眼就能看出握手节奏。
四次挥手:优雅地「说再见」
断开连接比建立连接复杂一些。为什么?因为TCP是全双工的,两边都要独立关闭自己的数据通道。
小提示:四次挥手不一定非得是四次报文。如果双方同时发起关闭,或者携带了数据,报文数量会变。但标准流程就是四次。
挥手流程拆解
| 步骤 | 方向 | 报文内容 | 状态变化 |
|---|---|---|---|
| 第一次挥手 | 客户端 → 服务端 | FIN, seq=u | 客户端 → FIN_WAIT_1 |
| 第二次挥手 | 服务端 → 客户端 | ACK, ack=u+1 | 服务端 → CLOSE_WAIT 客户端 → FIN_WAIT_2 |
| 第三次挥手 | 服务端 → 客户端 | FIN, seq=v | 服务端 → LAST_ACK |
| 第四次挥手 | 客户端 → 服务端 | ACK, ack=v+1 | 客户端 → TIME_WAIT 服务端 → CLOSED |
这里有个坑:第二次挥手和第三次挥手之间,服务端可能还在发数据。你想想看,服务端收到FIN后先回个ACK,说「我知道你要关了」,但自己还有数据没发完,那就继续发,发完了再发FIN。这就是为什么挥手可能是四次,而不是三次。
异常断开案例分析
理论讲完了,咱们看几个真实案例。这些我都亲手抓过包,踩过坑。
案例一:RST攻击式断开
正常的断开是FIN挥手,但有时候你会看到RST包。RST就是「我不玩了,直接断」。什么情况下会出现?
- 程序崩溃,操作系统发送RST
- 连接超时,一方主动RST
- 端口未监听,收到SYN后直接RST
我曾经排查过一个线上问题:客户端每隔几分钟就断连一次。抓包发现,服务端在收到请求后直接回了RST,而不是正常的响应。最后定位到是服务端的连接池配置错了,把空闲连接当成了异常连接给关了。
注意:RST包不需要ACK确认,发送方发完就完事了。所以RST断开比FIN断开快得多,但也更暴力——未发送的数据直接丢弃。
案例二:TIME_WAIT堆积
四次挥手的最后一个状态是TIME_WAIT。客户端发送完最后的ACK后,不会立刻关闭,而是等2MSL(Maximum Segment Lifetime,最大报文生存时间)。
为什么要等?两个原因:
- 怕最后的ACK丢了,服务端重发FIN时还能再回一次ACK
- 让旧连接的所有报文在网络中消失,避免干扰新连接
我记得有一次,一台高并发服务器上TIME_WAIT连接数飙到了几万个。新连接建不起来,因为端口都被占满了。解决方案?要么开启net.ipv4.tcp_tw_reuse,要么调整应用层连接池,减少短连接。
案例三:半关闭状态下的数据黑洞
还有一种情况:一方发了FIN,另一方回了ACK,但没发FIN。这时候连接处于半关闭状态。发FIN的一方不能再发数据,但还能收数据。
我在项目中遇到过:客户端调用shutdown(SHUT_WR)后,服务端以为客户端要彻底断开了,结果客户端还在等响应。服务端没发FIN,客户端就一直卡在FIN_WAIT_2。最后靠应用层超时机制才解了套。
核心知识图谱
下面这张图把TCP连接管理的核心逻辑串起来了。从建立到断开,再到异常处理,一目了然。
Wireshark实战:一眼识别异常
打开一个抓包文件,怎么快速判断连接有没有问题?我总结了几个经验:
- 看握手次数:如果只有两次握手(SYN→SYN+ACK),没有第三次ACK,说明连接没建起来。检查防火墙或服务端 backlog。
- 看挥手方式:正常是FIN→ACK→FIN→ACK。如果看到RST代替了FIN,说明有一方异常退出了。
- 看TIME_WAIT数量:在Wireshark的统计→对话里,如果某个IP对的连接大量处于TIME_WAIT,说明短连接太多,需要优化。
- 看重传:握手或挥手过程中出现TCP Retransmission,说明网络有丢包。这时候要检查链路质量。
实用技巧:在Wireshark里用 tcp.analysis.flags 这个过滤表达式,可以一次性标出所有异常——重传、丢包、乱序、窗口满,全在里面。我每次排查问题,第一件事就是跑这个过滤。
好了,TCP的连接管理就讲到这里。记住一句话:三次握手是「你行我也行」,四次挥手是「你先走我殿后」。抓包的时候,对照着这个思路看,基本不会跑偏。