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 状态转换图
下面这张图,是我自己画的状态机核心流程。你盯着看两分钟,比读十页书管用。
核心要点:状态机是对称的,但又不是完全对称的。主动关闭方要走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?两个原因:
- 保证最后一个ACK能到达对方。如果ACK丢了,对方会重传FIN,TIME_WAIT状态能重新发ACK。
- 让旧连接的所有报文在网络中消失,避免影响新连接。
我习惯把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状态才是真正干活的时候。记住这个逻辑,状态机就不难了。