type
status
date
slug
summary
tags
category
icon
password
硬核图解:TCP 三次握手与四次挥手全解析
在计算机网络的世界里,TCP(传输控制协议)是互联网的基石。无论是刷视频、玩游戏还是打开网页,TCP 都在底层默默地保障数据的可靠传输。
很多人背得下“三次握手、四次挥手”,但往往经不住面试官的深挖:“为什么握手是三次而不是两次?”、“那个大写的 ACK 和小写的 ack 到底啥关系?”。
本文将通过 Mermaid 时序图,结合底层原理,带你彻底搞懂这些细节。
🛠️ 第一部分:读懂 TCP 报文头——ACK 与 ack 的区别
在深入流程之前,我们必须先解决一个让无数初学者晕头转向的问题:大写 ACK 和小写 ack 有什么区别?
TCP 报文头中包含了很多字段,其中两个极其相似但功能完全不同的概念:
1. 标志位 (Flags) —— 大写的 ACK
- 性质:这是一个 1 bit 的开关(标志位)。
- 位置:位于 TCP 头部控制位(Control Bits)中。
- 作用:它像一个“开关”。
- 当
ACK = 1时,表示报文头部的 确认号 (ack) 字段是有效的。 - 当
ACK = 0时,确认号字段无效(通常只有连接刚开始建立的第一个 SYN 包,ACK 才是 0)。
- 一句话记忆:
ACK是一个动作指令,告诉对方“我要确认收货了”。
2. 确认号 (Acknowledgment Number) —— 小写的 ack
- 性质:这是一个 32 bit 的数值。
- 位置:位于 TCP 头部。
- 作用:它表示期望收到对方下一个报文段的第一个数据字节的序号。
- 如果你收到了对方发来的序号
100~200的数据,你的ack应该是201。
- 一句话记忆:
ack是具体的收货单号,告诉对方“201 之前的我都收到了,请发 201 及其之后的数据给我”。
总结:在握手和挥手过程中,除了第一个 SYN 包,后续所有包的 ACK 标志位都必须置为 1,而 ack 数值 则随着数据的传输不断累加。
🤝 第二部分:三次握手 (Three-Way Handshake)
TCP 建立连接需要三次交互。
📊 流程图解
Code snippet
❓ 核心面试题:为什么是三次握手?不是两次?不是四次?
这是一个经典的博弈逻辑问题。
1. 为什么不是“两次”?(防止历史连接的干扰)
- 场景:假设客户端发出的第一个 SYN 包在网络中滞留了(没丢,只是慢了)。客户端以为丢了,重发了第二个 SYN。
- 如果是两次握手:
- 服务端收到了滞留的旧 SYN,立刻回复 ACK 并建立连接。
- 客户端收到 ACK 后,发现这是个旧请求的回复,直接丢弃,不理服务端。
- 后果:服务端以为连接建好了,傻傻地等着客户端发数据,导致资源死锁/浪费。
- 如果是三次握手:
- 服务端收到旧 SYN,回复 SYN+ACK。
- 客户端收到后,根据序列号判断出这是个旧连接,回复
RST(重置)报文。 - 服务端收到 RST,关闭连接,避免了资源浪费。
- 结论:三次握手的主要目的是防止“已失效的连接请求”突然又传送到服务端,导致错误建立连接。
2. 为什么不是“四次”?(效率最优解)
- 理论上,双方建立连接可以分为 4 步:
- A 发送 SYN
- B 回复 ACK (确认收到 A 的 SYN)
- B 发送 SYN (B 也想连 A)
- A 回复 ACK (确认收到 B 的 SYN)
- 优化:TCP 协议允许将第 2 步和第 3 步合并。即 B 在确认 A 的同时,把自己的 SYN 也捎带过去(SYN+ACK 包)。
- 结论:四次没必要,三次刚刚好。
3. 为什么不是“二十次”?
- 工程设计讲究**“奥卡姆剃刀”**原则——如无必要,勿增实体。三次已经完美解决了“双方确认对方发送/接收能力”以及“同步序列号”的问题。再多就是浪费网络带宽和时间延迟。
👋 第三部分:四次挥手 (Four-Way Handshake)
TCP 连接是全双工的,这意味着数据传输是双向的。断开连接时,需要两个方向分别关闭。
📊 流程图解
Code snippet
❓ 核心面试题:为什么挥手需要四次?
- 握手是“合并”的,挥手为什么要“分开”?
- 在握手时,服务端在 LISTEN 状态下,可以立即将 SYN 和 ACK 合并发送。
- 但在挥手时,客户端发来 FIN,只代表客户端没数据发了。
- 此时,服务端可能还有数据需要处理和发送(比如正在处理数据库查询)。所以服务端必须先回一个
ACK("我知道你想走了"),等自己事情忙完了,再发一个FIN("我也好了,走吧")。 - 结论:因为半关闭 (Half-Close) 的存在,ACK 和 FIN 通常无法合并,必须分两次发。
💣 第四部分:高频面试死穴 —— TIME_WAIT 状态
如果面试官问 TCP,90% 的概率会问到
TIME_WAIT。1. 什么是 TIME_WAIT?
从上面的图可以看到,主动关闭连接的一方(通常是 Client),在发出最后一个 ACK 后,不会立即关闭,而是进入
TIME_WAIT 状态,并停留 2MSL(Maximum Segment Lifetime,最长报文段寿命,Linux 默认约 60秒)的时间。2. 为什么需要 TIME_WAIT?(必须背诵)
这里有两个核心原因:
- 原因 A:防止旧报文干扰新连接
- 如果不等待,客户端立刻关闭并立刻重启一个新的连接(用同样的 IP 和端口)。
- 此时,上一个连接中迷路在网络里的“旧数据包”突然到达了,新连接可能会混淆,把旧数据当成新数据接收,导致数据错乱。
- 等待 2MSL 可以保证本次连接产生的所有报文都在网络中消失。
- 原因 B:确保被动关闭方能收到最后的 ACK
- 假设客户端发完第 4 次挥手的 ACK 后直接跑路 (CLOSED)。
- 如果这个 ACK 在半路丢了,服务端收不到确认,就会触发超时重传,重发第三次挥手的 FIN。
- 此时客户端已经关闭了,收到重发的 FIN 会一脸懵逼,回复 RST(报错),导致服务端认为连接异常关闭。
- 等待的意义:如果 ACK 丢了,客户端还能在 TIME_WAIT 状态下收到重发的 FIN,并重新发送 ACK,确保服务端正常关闭。
3. 服务器出现大量 TIME_WAIT 怎么办?
- 现象:高并发场景下,如果服务器主动关闭连接(作为主动方),会产生大量 TIME_WAIT,占用端口资源。
- 解决:
- 调整内核参数
net.ipv4.tcp_tw_reuse = 1(允许复用 TIME_WAIT socket)。 - 尽量让客户端主动关闭连接(Web 服务器通常是被动方,不容易产生这个问题)。
📝 总结 CheckList
阶段 | 动作 | 核心知识点 |
握手 | 三次 | 防止历史连接复活;ACK标志是动作,ack数字是单号。 |
数据 | 传输 | seq 标记数据位置,ack 标记收到位置。 |
挥手 | 四次 | 全双工通道,需要双向分别关闭;FIN 表示发送结束。 |
结束 | TIME_WAIT | 主动关闭方进入;等待 2MSL;为防旧包干扰和保底最后的 ACK。 |
希望这篇文章能帮你构建起完整的 TCP 知识体系!
- Author:Ximou Zhao
- URL:https://ximouzhao.com/article/2d34b0ac-588b-8046-a789-f37f203c2fc6
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!


