外观
为什么建立连接要 3 次,而断开连接却要 4 次?
在上一课中我们看到,UDP 就像发短信,拿起手机打字就发,不管对方开没开机。
而 传输控制协议(TCP)(Transmission Control Protocol,传输控制协议) 就像给重要的客户打电话:
- 你拨通电话:“喂,你能听到我说话吗?”(第一次握手)
- 对方回复:“听得到!那你听得到我说话吗?”(第二次握手)
- 你回复:“我也听得到你,咱们开始谈合同吧!”(第三次握手)
直到双方都 100% 确认彼此的发话器和听筒都没问题,双向通信才正式确立。
更奇妙的是:建立通话只需上面这 3 句话,但到了要挂电话的时候,TCP 却规定必须走整整 4 步(四次挥手)!甚至主动挂电话的人在挂完之后,还要坐在椅子上干等几分钟(TIME_WAIT 状态)才允许做下一件事。
这背后到底藏着怎样的网络科学原理?本课将为你彻底解开 TCP 状态机的核心密码。
学完本课后,你将能够:
- 识记 TCP 首部 20 字节固定结构与 6 大核心控制标志位(SYN、ACK、FIN、RST、PSH、URG)。
- 完整推演 TCP 三次握手 的每一步序号变化,并能向任何人讲清楚“为什么两次握手不行、四次握手多余”。
- 精准推演 TCP 四次挥手 的四步时序与半关闭(Half-Close)状态。
- 深刻理解 为什么主动关闭方必须进入 TIME_WAIT 状态并坚守 2MSL 时间。
1. TCP 报文段的 20 字节固定首部
与 UDP 仅 8 字节的简陋首部不同,TCP 首部包含了极其强大的控制功能,最小固定长度为 20 字节(若包含可选字段可达 60 字节):
text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|R|Y|I| Window |
| (4b) | (6b) |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+必考字段精解:
- 序号(Sequence Number, 32 bit):本报文段所发送的数据的第一个字节的顺序号。TCP 是面向字节流的,每个字节都有独立编号。
- 确认号(Acknowledgment Number, 32 bit):期望收到对方下一个报文段的第一个数据字节的序号。若
ack = N,代表序号N - 1及以前的所有字节已全部收妥。 - 数据偏移(Data Offset, 4 bit):指出 TCP 首部共有多长(以 4 字节为单位计算)。默认 20 字节时该值为
(二进制 0101)。 - 6 个核心控制标志位(Flags):
- SYN(Synchronize):在建立连接时用来同步双方的初始序列号(ISN)。
- ACK(Acknowledgment):置 1 时,确认号(ack 字段)才合法有效。在连接建立后的所有正常交互中,ACK 恒置 1。
- FIN(Finish):用于释放连接,表示发送方数据已全发完,请求断开本方向通道。
- RST(Reset):连接发生严重错误(如端口未监听、主机崩溃),强制复位打断连接。
- PSH(Push):催促对端接收缓冲区立即将数据推给上层应用,不要干等缓冲区塞满。
- URG(Urgent):紧急指针有效,有高优先级数据优先处理(如 Ctrl+C 打断)。
- 窗口(Window, 16 bit):告诉对方“我本机的接收缓冲区还能装多少字节”,实现滑动窗口流量控制。
2. 三次握手全时序推演
连接建立的核心目的有两个:让双方就初始序号(ISN)达成一致,并确认双向收发信道均畅通。
第一步:客户端主动发起握手(SYN)
- 客户端随机生成一个初始序列号
。 - 组装 TCP 报文,标志位
SYN = 1,ACK = 0,不携带任何应用层数据。 - 客户端套接字状态从
CLOSED变为SYN_SENT。
第二步:服务端同意建链并反向请求(SYN + ACK)
- 服务端收到 SYN 报文,得知客户端的初始序号是
。 - 服务端自己也随机生成一个本端的初始序列号
。 - 回应报文将标志位同时置位:
SYN = 1,ACK = 1。 - 填入确认号
ack = x + 1(表示“你的第序号已收到,接下来请从 开始发”)。 - 服务端套接字状态从
LISTEN变为SYN_RCVD。
第三步:客户端确认并完成建链(ACK)
- 客户端收到服务端的 SYN+ACK,确认服务端已就绪且获知了服务端的初始序号
。 - 客户端发送最终确认报文:
SYN = 0,ACK = 1,序号,确认号 ack = y + 1。 - 关键细节:这第三次握手的报文已经允许携带正常的应用层数据了(比如 HTTP 请求行)。
- 客户端状态进入
ESTABLISHED(已建立);服务端收到该 ACK 后,状态也进入ESTABLISHED。
3. 为什么必须是三次?两次行不行?
这是所有网络面试与华为 ICT 大赛中最经典的高频灵魂提问:“两次握手难道不够吗?”
两次握手的致命缺陷:失效历史报文引起的灾难
设想一种网络极端延迟场景:
- 客户端发送了一个连接请求报文 SYN1。但是因为网络骨干路由器拥堵,SYN1 被滞留在某个路由器的缓冲队列里长达数分钟。
- 客户端超时未收到回应,判定连接失败,放弃了这次尝试。
- 过了很久,网络恢复通畅,这个滞留的“老僵尸”SYN1 终于慢悠悠送达了服务端。
- 如果采用两次握手:服务端一收到 SYN1,立即认为客户端又想建链,回复一个 ACK 并单方面进入
ESTABLISHED状态,为该连接分配内存缓冲区与资源。 - 但客户端早就翻篇了,根本不认这个连接,绝不会给服务端发送任何数据;而服务端却傻傻地一直维持着这个幽灵连接,白白耗死服务器资源!
三次握手的妙处就在于:服务端发回 SYN+ACK 之后,必须等待客户端的最终 ACK 确认。客户端收到服务端针对“老僵尸”的应答时,一查序号发现不是自己当前要的,就会断然发送一个 RST 报文,命令服务端立即释放该连接,绝不会被历史延迟报文愚弄。
4. 四次挥手全时序推演
建立连接只需三次,为什么断开连接却要整整四次? 根本原因在于:TCP 是全双工协议,数据通道是双向独立的! “我不想给你发数据了(我的发送通道关闭)”,并不代表“你也不能给我发数据了(你的发送通道依然可以工作)”。
第一步:主动方请求关闭(FIN)
- 客户端调用
close(),向服务端发送FIN = 1,ACK = 1,。 - 客户端不再发送应用数据,状态从
ESTABLISHED进入FIN_WAIT_1。
第二步:被动方确认收到(ACK)
- 服务端收到 FIN,立刻回复
ACK = 1,。 - 服务端状态变为
CLOSE_WAIT;客户端收到该 ACK 后变为FIN_WAIT_2。 - 半关闭状态(Half-Close):此时客户端到服务端的单向通道已关闭,但服务端如果还有没传完的数据,依然可以继续向客户端发送,客户端依然必须接收!
第三步:被动方传完全部数据,发起关闭(FIN)
- 服务端把剩余的最后一点数据全部发完,确认无事可做,也调用
close()。 - 向客户端发送
FIN = 1,ACK = 1,。 - 服务端状态进入
LAST_ACK。
第四步:主动方最终确认(ACK 与 TIME_WAIT)
- 客户端收到服务端的 FIN,回复最终确认
ACK = 1,。 - 服务端收到该 ACK 后,彻底完成关闭,进入
CLOSED。 - 主动关闭的客户端并不立即关闭,而是进入极其关键的
TIME_WAIT状态,等待 2MSL 时间后才最终进入CLOSED!
5. 灵魂拷问:TIME_WAIT 为什么必须苦等 2MSL?
MSL(Maximum Segment Lifetime,报文最大生存时间),工程上通常设为 30 秒到 2 分钟。2MSL 就意味着主动方要在 TIME_WAIT 状态静等 1 到 4 分钟。为什么不能一发完 ACK 就拔腿就走?
有两个不可替代的保命理由:
理由一:保证最后一个 ACK 能够安全送达服务端
- 互联网信道是不稳定的。如果客户端发出的第 4 次 ACK 在半路丢包了:
- 服务端在
LAST_ACK状态迟迟收不到确认,就会超时重发第 3 步的 FIN 报文。 - 如果客户端发完 ACK 就立马撒手不管进入
CLOSED,那么收到重发的 FIN 时只能冷冰冰回复一个 RST 报错,导致服务端无法正常关闭,甚至判定传输发生严重故障。 - 保持 2MSL 的 TIME_WAIT,可以让客户端在收到重发的 FIN 时,有能力再补发一次第 4 步的 ACK,确保服务端能够安详关闭!
理由二:让本次连接在网络中残留的所有老报文完全消亡
- 2MSL 正好等于一个报文在网络中往返一趟的最大极限时间。
- 经过 2MSL 后,本次会话的所有旧分片在互联网路由器中因 TTL 超时或自然消亡被清空得干干净净。
- 这样,未来相同的源 IP 和源端口号被下一个新进程重新复用时,绝对不会收到来自上一个旧连接的迟到垃圾数据包,杜绝了数据串扰风险。
6. 课后核心测验
7. 下一步学习
连接建立之后,双方开始通过全双工信道疯狂倾泻数据。
面对可能丢包的脆弱物理介质、速度参差不齐的接收端主机、以及随时可能拥堵瘫痪的互联网骨干网,TCP 究竟是如何施展魔法,把一个不可靠的网络改造成一个绝对可靠、永不丢字、自动调速的钢铁信道的?
下一课,让我们深入攻坚:第 25 课 · TCP 可靠传输:滑动窗口、超时重传与拥塞控制!