TCP三次握手与四次挥手:SYN、ACK、FIN标志位详解

TCP的连接管理,说白了就是三次握手和四次挥手。这两个过程是网络通信的基石,也是抓包分析中最常遇到的场景。我刚开始做网络排查时,总觉得这些理论太抽象,直到有一次线上服务频繁超时,抓包一看——握手阶段就卡住了。嗯,从那以后我再也不敢小看这几个标志位了。

TCP标志位:控制连接的开关

TCP头部中有6个标志位,但最核心的就是三个:SYN、ACK、FIN。它们就像交通信号灯,控制着连接的建立、数据传输和关闭。

标志位 全称 作用 典型场景
SYN Synchronize 发起连接请求,同步序列号 三次握手的第1、2步
ACK Acknowledgment 确认收到数据,携带确认号 几乎每个包都有(除了SYN本身)
FIN Finish 发起连接关闭请求 四次挥手的第1、3步
我的习惯:抓包时先看标志位组合。比如「SYN-ACK」一定是服务端回复的,「ACK」单独出现多半是数据传输的确认。一眼就能判断当前处于哪个阶段。

三次握手:建立连接的完整过程

三次握手,说白了就是客户端和服务端互相确认「我准备好了,你呢?」。这个过程涉及三个报文,每个报文都携带关键信息。

握手流程详解

  1. 第一次握手(SYN):客户端发送SYN包,随机生成一个初始序列号(比如seq=1000),进入SYN_SENT状态。
  2. 第二次握手(SYN-ACK):服务端收到后,回复SYN-ACK包。确认号是客户端的seq+1(ack=1001),同时带上自己的初始序列号(seq=2000),进入SYN_RCVD状态。
  3. 第三次握手(ACK):客户端再回复ACK包,确认号是服务端的seq+1(ack=2001),进入ESTABLISHED状态。服务端收到后也进入ESTABLISHED。

核心要点:三次握手的本质是双方各自确认对方的接收能力和发送能力都正常。两次不够,四次浪费,三次刚刚好。

握手状态机

TCP的状态机其实不复杂。我画了一张图,把三次握手的流转路径标清楚了。你跟着箭头走一遍,基本就记住了。

TCP三次握手状态机 CLOSED SYN_SENT ESTABLISHED CLOSED SYN_RCVD ESTABLISHED 客户端发起连接 SYN (seq=1000) SYN-ACK (seq=2000, ack=1001) ACK (seq=1001, ack=2001) 关键点:第三次握手丢失会导致服务端一直处于 SYN_RCVD 状态,最终超时重传
我曾经踩过的坑:有一次排查连接超时,抓包发现只有前两次握手,第三次ACK始终没到。后来发现是客户端的防火墙把出站SYN-ACK给拦截了。所以抓包时一定要在两端都抓,才能定位问题在哪一侧。

四次挥手:优雅关闭连接

四次挥手比握手复杂一些,因为TCP是全双工的,每一侧都需要单独关闭。说白了就是「我说完了,你继续说;你说完了,我再关」。

挥手流程详解

  1. 第一次挥手(FIN):主动关闭方发送FIN包,进入FIN_WAIT_1状态。
  2. 第二次挥手(ACK):被动关闭方回复ACK,进入CLOSE_WAIT状态。主动方收到后进入FIN_WAIT_2。
  3. 第三次挥手(FIN):被动关闭方发送自己的FIN包,进入LAST_ACK状态。
  4. 第四次挥手(ACK):主动关闭方回复ACK,进入TIME_WAIT状态,等待2MSL后关闭。

注意:第二次和第三次挥手之间,被动关闭方可能还有数据要发送。所以挥手不一定是连续的四个包,中间可能夹杂数据传输。

挥手状态机

我再用一张图把挥手的流转画出来。你注意看TIME_WAIT这个状态,它经常是面试题的重点,也是实际排查中的关键点。

TCP四次挥手状态机 ESTABLISHED FIN_WAIT_1 FIN_WAIT_2 TIME_WAIT CLOSED ESTABLISHED CLOSE_WAIT LAST_ACK CLOSED 应用层调用 close() FIN (seq=1000) ACK (ack=1001) FIN (seq=2000) ACK (ack=2001) TIME_WAIT 持续 2MSL(约 2 分钟),确保最后一个 ACK 能被对方收到

常见异常场景分析

理论说完了,咱们聊聊实战中经常遇到的坑。我这些年排查过的网络问题,十有八九都跟握手或挥手异常有关。

场景一:半连接攻击(SYN Flood)

攻击者只发SYN,不回复第三次握手。服务端会一直保留SYN_RCVD状态的连接,直到超时。如果并发量大了,服务端的半连接队列会被塞满,正常用户连不上。

排查方法:抓包看到大量SYN包,但没有对应的SYN-ACK回复,或者SYN-ACK重传多次后放弃。用netstat -s看SYN_RCVD数量是否异常。

场景二:CLOSE_WAIT 堆积

被动关闭方收到FIN后,回复了ACK,但应用层没有调用close()。这时候连接会一直卡在CLOSE_WAIT状态。我遇到过最夸张的一次,一台服务器上有几万个CLOSE_WAIT,直接把端口耗尽了。

我曾经踩过的坑:有一次排查HTTP服务不可用,发现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的配对。只要标志位和序列号对得上,连接就没问题。对不上,就是问题所在。