TCP三次握手与四次挥手:SYN、ACK、FIN标志位详解
TCP的连接管理,说白了就是三次握手和四次挥手。这两个过程是网络通信的基石,也是抓包分析中最常遇到的场景。我刚开始做网络排查时,总觉得这些理论太抽象,直到有一次线上服务频繁超时,抓包一看——握手阶段就卡住了。嗯,从那以后我再也不敢小看这几个标志位了。
TCP标志位:控制连接的开关
TCP头部中有6个标志位,但最核心的就是三个:SYN、ACK、FIN。它们就像交通信号灯,控制着连接的建立、数据传输和关闭。
| 标志位 | 全称 | 作用 | 典型场景 |
|---|---|---|---|
| SYN | Synchronize | 发起连接请求,同步序列号 | 三次握手的第1、2步 |
| ACK | Acknowledgment | 确认收到数据,携带确认号 | 几乎每个包都有(除了SYN本身) |
| FIN | Finish | 发起连接关闭请求 | 四次挥手的第1、3步 |
三次握手:建立连接的完整过程
三次握手,说白了就是客户端和服务端互相确认「我准备好了,你呢?」。这个过程涉及三个报文,每个报文都携带关键信息。
握手流程详解
- 第一次握手(SYN):客户端发送SYN包,随机生成一个初始序列号(比如seq=1000),进入
SYN_SENT状态。 - 第二次握手(SYN-ACK):服务端收到后,回复SYN-ACK包。确认号是客户端的seq+1(ack=1001),同时带上自己的初始序列号(seq=2000),进入
SYN_RCVD状态。 - 第三次握手(ACK):客户端再回复ACK包,确认号是服务端的seq+1(ack=2001),进入
ESTABLISHED状态。服务端收到后也进入ESTABLISHED。
核心要点:三次握手的本质是双方各自确认对方的接收能力和发送能力都正常。两次不够,四次浪费,三次刚刚好。
握手状态机
TCP的状态机其实不复杂。我画了一张图,把三次握手的流转路径标清楚了。你跟着箭头走一遍,基本就记住了。
四次挥手:优雅关闭连接
四次挥手比握手复杂一些,因为TCP是全双工的,每一侧都需要单独关闭。说白了就是「我说完了,你继续说;你说完了,我再关」。
挥手流程详解
- 第一次挥手(FIN):主动关闭方发送FIN包,进入
FIN_WAIT_1状态。 - 第二次挥手(ACK):被动关闭方回复ACK,进入
CLOSE_WAIT状态。主动方收到后进入FIN_WAIT_2。 - 第三次挥手(FIN):被动关闭方发送自己的FIN包,进入
LAST_ACK状态。 - 第四次挥手(ACK):主动关闭方回复ACK,进入
TIME_WAIT状态,等待2MSL后关闭。
注意:第二次和第三次挥手之间,被动关闭方可能还有数据要发送。所以挥手不一定是连续的四个包,中间可能夹杂数据传输。
挥手状态机
我再用一张图把挥手的流转画出来。你注意看TIME_WAIT这个状态,它经常是面试题的重点,也是实际排查中的关键点。
常见异常场景分析
理论说完了,咱们聊聊实战中经常遇到的坑。我这些年排查过的网络问题,十有八九都跟握手或挥手异常有关。
场景一:半连接攻击(SYN Flood)
攻击者只发SYN,不回复第三次握手。服务端会一直保留SYN_RCVD状态的连接,直到超时。如果并发量大了,服务端的半连接队列会被塞满,正常用户连不上。
netstat -s看SYN_RCVD数量是否异常。
场景二:CLOSE_WAIT 堆积
被动关闭方收到FIN后,回复了ACK,但应用层没有调用close()。这时候连接会一直卡在CLOSE_WAIT状态。我遇到过最夸张的一次,一台服务器上有几万个CLOSE_WAIT,直接把端口耗尽了。
CLOSE_WAIT数量持续增长。最后定位到是代码里忘了关闭Socket输入流。所以写网络程序时,一定要确保finally块里关闭连接。
场景三:TIME_WAIT 过多
主动关闭方在发送最后一个ACK后会进入TIME_WAIT,持续2MSL(约2分钟)。如果短连接并发量很大,TIME_WAIT会堆积,占用大量内存和端口。
为什么会这样?你想想看,如果最后一个ACK丢了,被动关闭方会重传FIN。没有TIME_WAIT的话,主动方收到重传的FIN后无法正确回复。所以这个状态是必要的,但确实会带来性能问题。
| 异常场景 | 抓包特征 | 常见原因 | 解决思路 |
|---|---|---|---|
| SYN Flood | 大量SYN,无第三次握手 | DDoS攻击 | 启用SYN Cookie、限制半连接数 |
| CLOSE_WAIT堆积 | 连接卡在CLOSE_WAIT | 应用层未关闭Socket | 检查代码,确保finally关闭 |
| TIME_WAIT过多 | 大量TIME_WAIT连接 | 短连接并发高 | 开启端口复用、使用长连接 |
| 握手超时 | SYN重传多次后放弃 | 网络不通、防火墙拦截 | 检查路由、防火墙规则 |
抓包实战技巧
最后分享几个我常用的抓包技巧,帮你快速定位握手/挥手问题。
- 过滤表达式:抓握手用
tcp.flags.syn==1,抓挥手用tcp.flags.fin==1。组合起来用tcp.flags.syn==1 and tcp.flags.ack==0只看纯SYN包。 - 看序列号:三次握手的seq和ack是连续的。如果发现seq跳变或者ack异常,说明有丢包或乱序。
- 看重传:Wireshark里标记为
[TCP Retransmission]的包,说明对方没收到确认。重传次数多了,基本可以断定网络有问题。 - 两端同时抓:我习惯在客户端和服务端同时抓包,对比时间戳。如果客户端发了SYN但服务端没收到,问题出在网络中间。
总结一句话:三次握手看SYN和ACK的配对,四次挥手看FIN和ACK的配对。只要标志位和序列号对得上,连接就没问题。对不上,就是问题所在。