TCP 三次握手与四次挥手详解
面试官最爱问的网络题,没有之一。看完这篇,把"为什么是三次而不是两次""为什么挥手要四次""TIME_WAIT 为什么要等 2MSL"一次讲透。
开场:为什么是三次而不是两次?
想象一个场景:你和朋友约定明天一起爬山。
- 你说:"明天去爬山吧?"(第一次握手:SYN)
- 朋友回:"好啊,几点?"(第二次握手:SYN+ACK)
- 你再说:"那就早上 8 点见!"(第三次握手:ACK)
三次对话,双方才确认"你愿意去、我愿意去、约定达成"。如果只有两次——你说"去爬山",朋友回"好"——你怎么确定朋友真的收到了你的确认?
TCP 的三次握手,本质就是同步双方初始序号(ISN) 的过程,让双方都确认"对方的接收能力正常、自己的发送能力正常",从而建立起一条可靠的端到端连接。
一句话记忆
三次握手 = 确认双方收发能力都正常。第一次证明"客户端能发、服务端能收",第二次证明"服务端能发、客户端能收",第三次确认"双方约定达成"。
一、三次握手全流程
1. 时序图
Client Server
| |
|──────── SYN, seq=x ────────────────→| ① 客户端发送 SYN
| 客户端进入 SYN_SENT | 请求建立连接,同步初始序号 x
| |
|←────── SYN, seq=y, ack=x+1 ────────| ② 服务端回复 SYN+ACK
| 服务端进入 SYN_RCVD | 同意连接,发送自己的 ISN=y
| | 并确认收到客户端的 x
| |
|──────── ACK, seq=x+1, ack=y+1 ────→| ③ 客户端发送 ACK
| 双方进入 ESTABLISHED | 确认收到服务端的 y,连接建立
| |2. 三步状态变化
| 步骤 | 报文 | 作用 | 状态变化 |
|---|---|---|---|
| ① | 客户端 → 服务端:SYN=1, seq=x | 请求建立连接,同步初始序号 x | CLOSED → SYN_SENT |
| ② | 服务端 → 客户端:SYN=1, seq=y, ack=x+1 | 服务端同意连接,发送自己的 ISN=y,确认收到 x | LISTEN → SYN_RCVD |
| ③ | 客户端 → 服务端:ACK=1, seq=x+1, ack=y+1 | 确认收到服务端的 ISN,完成建立 | SYN_SENT → ESTABLISHED |
关于序号
seq=x 表示本报文段第一个字节的序号;ack=x+1 表示"我期望收到的下一个字节序号是 x+1",即确认收到了 x。注意第二次握手同时携带 SYN 和 ACK,这是握手只有三次而非四次的关键。
3. 为什么必须是三次,两次不行?
这是面试最高频的追问。核心原因:防止"已失效的连接请求"突然到达服务端,造成资源浪费。
假设只有两次握手:
- 客户端发送 SYN=1 的请求,但在网络中滞留超时(比如网络拥堵),客户端等不到回应,超时后重发 SYN 并成功建立了连接,正常通信、正常关闭。
- 此时第一个滞留的 SYN 才姗姗到达服务端。服务端以为客户端又发起了一次新连接,于是回复 SYN+ACK,并分配资源等待——可客户端根本没有这个意图,服务端的资源被白白占用。
而有了第三次握手:
- 客户端收到服务端的 SYN+ACK 后,判断这是否是自己发起的连接,再回复 ACK。
- 对于上述"幽灵请求",客户端不会理睬,服务端收不到 ACK 自然就释放资源,不会建立无效连接。
面试加分点
三次握手的第三个 ACK 报文可以携带数据(前两个不行,因为此时序号还未同步完成),这也是 TCP 建立连接后立即就能高效传输数据的原因之一。
二、四次挥手全流程
1. 时序图
TCP 连接是全双工的——双方可以同时收发数据,因此每一方的数据流都要独立关闭,这就是挥手必须四次的原因。
Client Server
| |
|──────── FIN, seq=u ────────────────→| ① 客户端发送 FIN
| 客户端进入 FIN_WAIT_1 | 不再发送数据,请求释放连接
| |
|←────── ACK, seq=v, ack=u+1 ────────| ② 服务端回复 ACK
| 服务端进入 CLOSE_WAIT | 确认收到 FIN
| 客户端进入 FIN_WAIT_2 | 但可能还有数据要发送
| | (半关闭状态)
|←────── FIN, seq=w, ack=u+1 ────────| ③ 服务端发送 FIN
| 服务端进入 LAST_ACK | 数据发送完毕,请求释放连接
| |
|──────── ACK, seq=u+1, ack=w+1 ────→| ④ 客户端发送 ACK
| 客户端进入 TIME_WAIT(2MSL) | 确认收到 FIN
| 服务端进入 CLOSED | 连接正式释放
| |2. 四步状态变化
| 步骤 | 报文 | 状态变化 |
|---|---|---|
| ① | 客户端 → 服务端:FIN=1, seq=u | 客户端不再发送数据,发出连接释放报文 |
| ② | 服务端 → 客户端:ACK=1, seq=v, ack=u+1 | 服务端确认收到 FIN,但可能还有数据要发(半关闭状态) |
| ③ | 服务端 → 客户端:FIN=1, ACK=1, seq=w, ack=u+1 | 服务端数据发送完毕,发送 FIN 请求释放连接 |
| ④ | 客户端 → 服务端:ACK=1, seq=u+1, ack=w+1 | 客户端确认收到 FIN,进入 TIME_WAIT(等待 2MSL) |
3. 为什么不是三次挥手?
关键在第二步和第三步之间:服务端收到 FIN 后,只是确认收到,并不立即关闭——它可能还有数据没发完。
- 如果服务端没有数据要发了,理论上可以把 ② 和 ③ 合并成一个报文,即"三次挥手"(FIN+ACK 一起发)。
- 但在实际传输中,服务端通常需要时间处理剩余数据,所以 ②③ 必须分开,形成了标准的四次挥手。
半关闭状态(Half-Close)
第二次挥手后,连接进入半关闭:客户端 → 服务端方向已关闭(客户端不再发数据),但 服务端 → 客户端方向仍可继续发送。这也是 TCP 全双工特性的体现。
三、TIME_WAIT 为什么要等 2MSL?
第四次挥手后,主动关闭的一方(通常是客户端)会进入 TIME_WAIT 状态,等待 2MSL(Maximum Segment Lifetime,最大报文段生存时间) 后才真正关闭。MSL 是报文在网络中的最长存活时间(RFC 建议 2 分钟,Linux 默认约 30~60 秒)。
原因有二:
确保最后一个 ACK 到达服务端。如果 ACK 在网络中丢失,服务端会因超时重发 FIN;客户端在 2MSL 内可以收到重发的 FIN 并再次回复 ACK。若不等 2MSL 直接关闭,服务端将永远收不到确认,无法正常关闭连接。
让本次连接的所有报文在网络中消失。2MSL 足够一个报文从发出到被彻底丢弃(一来一回各 1 个 MSL),防止旧连接的"残骸"(如延迟到达的报文)干扰使用相同四元组的新连接。
面试易错点
TIME_WAIT 只出现在主动关闭方。大量短连接场景(如高并发服务)可能出现 TIME_WAIT 堆积,可通过开启 tcp_tw_reuse 或调整 MSL 参数缓解,但这是另一个进阶话题了。
四、完整状态机一览
| 状态 | 出现时机 | 说明 |
|---|---|---|
| CLOSED | 初始 | 无连接状态 |
| LISTEN | 服务端 | 监听端口,等待连接 |
| SYN_SENT | 客户端发 SYN 后 | 等待服务端 SYN+ACK |
| SYN_RCVD | 服务端收 SYN 后 | 等待客户端 ACK |
| ESTABLISHED | 三次握手完成后 | 连接建立,正常传输数据 |
| FIN_WAIT_1 | 客户端发 FIN 后 | 等待服务端 ACK |
| FIN_WAIT_2 | 客户端收 ACK 后 | 等待服务端 FIN |
| CLOSE_WAIT | 服务端收 FIN 后 | 等待应用层关闭 |
| LAST_ACK | 服务端发 FIN 后 | 等待客户端最后的 ACK |
| TIME_WAIT | 客户端发最后 ACK 后 | 等待 2MSL 后关闭 |
| CLOSED | 2MSL 结束 | 回到初始状态 |
总结
三次握手:SYN → SYN+ACK → ACK,核心是同步 ISN、确认双方收发能力,第三次 ACK 有效防止"失效连接请求"浪费资源。
四次挥手:FIN → ACK → FIN → ACK,因为连接全双工、每侧数据流独立关闭;中间的半关闭状态允许未发完的数据继续传输。
TIME_WAIT 等 2MSL:一是兜底保证最后一个 ACK 不丢,二是让旧报文在网络中彻底消失,保护新连接。
把这套逻辑串成一条线:建连三次、断连四次、主动关闭方多等 2MSL——面试问到,先画时序图,再答"为什么",稳了。
参考资料
- 传输层笔记(完整版) — 含 TCP/UDP 对比、端口号、TCP 首部格式、可靠传输、流量控制、拥塞控制等完整内容
