4、TCP状态机总览:11种状态与连接管理

TCP状态机,说白了就是一张“连接生命周期地图”。

我刚开始学TCP的时候,觉得这11个状态特别绕。后来在项目里抓包排查过几次连接异常,才真正理解——每个状态都不是凭空设计的,它们对应着连接建立、数据传输、连接释放的每一个关键时刻。

4.1 11种状态速览

先列个总表,方便你对照。这11个状态,我习惯把它们分成三组:

状态 所属阶段 一句话描述
CLOSED 初始/结束 没有连接,啥也不干
LISTEN 建立 服务端等着客户端来连
SYN_SENT 建立 客户端发了SYN,等回复
SYN_RCVD 建立 服务端收到SYN,发了SYN+ACK,等确认
ESTABLISHED 数据传输 连接已建立,可以收发数据
FIN_WAIT_1 释放 主动方发了FIN,等对方的ACK或FIN
FIN_WAIT_2 释放 主动方收到ACK,等对方的FIN
CLOSE_WAIT 释放 被动方收到FIN,等上层应用关闭
LAST_ACK 释放 被动方发了FIN,等最后的ACK
TIME_WAIT 释放 主动方收到FIN,等2MSL后彻底关闭
CLOSING 释放 双方同时发FIN,等对方的ACK
我的习惯:记状态机时,先记住CLOSED和ESTABLISHED这两个“稳定态”,其他9个都是“过渡态”。过渡态要么在建立连接的路上,要么在释放连接的路上。

4.2 状态转换图

下面这张图,是我自己画的状态机核心流程。你盯着看两分钟,比读十页书管用。

TCP状态机核心转换图 TCP状态机核心转换图 CLOSED LISTEN SYN_SENT SYN_RCVD ESTABLISHED FIN_WAIT_1 CLOSE_WAIT FIN_WAIT_2 LAST_ACK TIME_WAIT 被动打开 主动打开 发SYN 收SYN 收SYN+ACK 收ACK 主动关闭 收FIN 收ACK 收FIN+ACK 收FIN 应用关闭 收ACK 2MSL超时 说明:实线箭头表示客户端主动关闭路径,虚线箭头表示服务端被动关闭路径(图中未区分线型,实际以方向为准)

核心要点:状态机是对称的,但又不是完全对称的。主动关闭方要走FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT,被动关闭方走CLOSE_WAIT → LAST_ACK。TIME_WAIT是主动方的“专属待遇”。

4.3 状态机与连接管理的关系

状态机不是摆设,它直接决定了连接能不能建起来、能不能正常断开。

4.3.1 连接建立阶段

  • 三次握手:CLOSED → SYN_SENT → ESTABLISHED(客户端),CLOSED → LISTEN → SYN_RCVD → ESTABLISHED(服务端)
  • 我踩过的坑:曾经有个服务端程序,LISTEN状态的socket忘了调用accept(),结果客户端SYN_SENT一直重传,最后超时。抓包一看,服务端SYN_RCVD状态卡住了,因为内核的backlog队列满了。

4.3.2 连接释放阶段

  • 四次挥手:主动方ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
  • 被动方:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
  • 同时关闭:双方都发FIN,进入CLOSING状态,然后都进TIME_WAIT
⚠️ 注意:CLOSE_WAIT状态是很多线上问题的根源。如果应用层不调用close(),socket会一直卡在CLOSE_WAIT。我曾经排查过一个连接泄漏问题,netstat一看,几百个CLOSE_WAIT挂在那里——就是代码里忘了释放资源。

4.3.3 TIME_WAIT的意义

为什么主动关闭方要等2MSL?两个原因:

  1. 保证最后一个ACK能到达对方。如果ACK丢了,对方会重传FIN,TIME_WAIT状态能重新发ACK。
  2. 让旧连接的所有报文在网络中消失,避免影响新连接。

我习惯把TIME_WAIT理解成“安全缓冲期”。2MSL大约是1-4分钟(取决于系统实现),这段时间内同一个五元组不能复用。

4.4 常见问题与避坑

问题 原因 我的建议
大量TIME_WAIT 高并发短连接,主动关闭方太多 考虑复用连接,或调整tcp_tw_reuse参数
大量CLOSE_WAIT 应用层没调用close() 检查代码,确保每个socket都有对应的关闭逻辑
SYN flooding 攻击者发大量SYN,不完成握手 开启SYN Cookie,或调整tcp_syncookies
连接卡在FIN_WAIT_2 对端不发送FIN 设置tcp_fin_timeout,超时后强制关闭
一个小技巧:用netstat -tan | awk '{print $6}' | sort | uniq -c可以快速统计当前所有TCP状态的数量。我每次排查连接问题,第一件事就是跑这个命令。

嗯,状态机就讲到这里。你想想看,11个状态其实就干了两件事:建立连接、释放连接。中间ESTABLISHED状态才是真正干活的时候。记住这个逻辑,状态机就不难了。