跳转到正文

为什么建立连接要 3 次,而断开连接却要 4 次? ​

在上一课中我们看到,UDP 就像发短信,拿起手机打字就发,不管对方开没开机。

而 传输控制协议(TCP)(Transmission Control Protocol,传输控制协议) 就像给重要的客户打电话:

  1. 你拨通电话:“喂,你能听到我说话吗?”(第一次握手)
  2. 对方回复:“听得到!那你听得到我说话吗?”(第二次握手)
  3. 你回复:“我也听得到你,咱们开始谈合同吧!”(第三次握手)

直到双方都 100% 确认彼此的发话器和听筒都没问题,双向通信才正式确立。

更奇妙的是:建立通话只需上面这 3 句话,但到了要挂电话的时候,TCP 却规定必须走整整 4 步(四次挥手)!甚至主动挂电话的人在挂完之后,还要坐在椅子上干等几分钟(TIME_WAIT 状态)才允许做下一件事。

这背后到底藏着怎样的网络科学原理?本课将为你彻底解开 TCP 状态机的核心密码。


学完本课后,你将能够:

  1. 识记 TCP 首部 20 字节固定结构与 6 大核心控制标志位(SYN、ACK、FIN、RST、PSH、URG)。
  2. 完整推演 TCP 三次握手 的每一步序号变化,并能向任何人讲清楚“为什么两次握手不行、四次握手多余”。
  3. 精准推演 TCP 四次挥手 的四步时序与半关闭(Half-Close)状态。
  4. 深刻理解 为什么主动关闭方必须进入 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) ...                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

必考字段精解: ​

  1. 序号(Sequence Number, 32 bit):本报文段所发送的数据的第一个字节的顺序号。TCP 是面向字节流的,每个字节都有独立编号。
  2. 确认号(Acknowledgment Number, 32 bit):期望收到对方下一个报文段的第一个数据字节的序号。若 ack = N,代表序号 N - 1 及以前的所有字节已全部收妥。
  3. 数据偏移(Data Offset, 4 bit):指出 TCP 首部共有多长(以 4 字节为单位计算)。默认 20 字节时该值为 20/4=5(二进制 0101)。
  4. 6 个核心控制标志位(Flags):
    • SYN(Synchronize):在建立连接时用来同步双方的初始序列号(ISN)。
    • ACK(Acknowledgment):置 1 时,确认号(ack 字段)才合法有效。在连接建立后的所有正常交互中,ACK 恒置 1。
    • FIN(Finish):用于释放连接,表示发送方数据已全发完,请求断开本方向通道。
    • RST(Reset):连接发生严重错误(如端口未监听、主机崩溃),强制复位打断连接。
    • PSH(Push):催促对端接收缓冲区立即将数据推给上层应用,不要干等缓冲区塞满。
    • URG(Urgent):紧急指针有效,有高优先级数据优先处理(如 Ctrl+C 打断)。
  5. 窗口(Window, 16 bit):告诉对方“我本机的接收缓冲区还能装多少字节”,实现滑动窗口流量控制。

2. 三次握手全时序推演 ​

连接建立的核心目的有两个:让双方就初始序号(ISN)达成一致,并确认双向收发信道均畅通。

TCP 握手与挥手状态时序图

第一步:客户端主动发起握手(SYN) ​

  • 客户端随机生成一个初始序列号 seq=x。
  • 组装 TCP 报文,标志位 SYN = 1,ACK = 0,不携带任何应用层数据。
  • 客户端套接字状态从 CLOSED 变为 SYN_SENT。

第二步:服务端同意建链并反向请求(SYN + ACK) ​

  • 服务端收到 SYN 报文,得知客户端的初始序号是 x。
  • 服务端自己也随机生成一个本端的初始序列号 seq=y。
  • 回应报文将标志位同时置位:SYN = 1,ACK = 1。
  • 填入确认号 ack = x + 1(表示“你的第 x 序号已收到,接下来请从 x+1 开始发”)。
  • 服务端套接字状态从 LISTEN 变为 SYN_RCVD。

第三步:客户端确认并完成建链(ACK) ​

  • 客户端收到服务端的 SYN+ACK,确认服务端已就绪且获知了服务端的初始序号 y。
  • 客户端发送最终确认报文:SYN = 0,ACK = 1,序号 seq=x+1,确认号 ack = y + 1。
  • 关键细节:这第三次握手的报文已经允许携带正常的应用层数据了(比如 HTTP 请求行)。
  • 客户端状态进入 ESTABLISHED(已建立);服务端收到该 ACK 后,状态也进入 ESTABLISHED。

3. 为什么必须是三次?两次行不行? ​

这是所有网络面试与华为 ICT 大赛中最经典的高频灵魂提问:“两次握手难道不够吗?”

两次握手的致命缺陷:失效历史报文引起的灾难 ​

设想一种网络极端延迟场景:

  1. 客户端发送了一个连接请求报文 SYN1。但是因为网络骨干路由器拥堵,SYN1 被滞留在某个路由器的缓冲队列里长达数分钟。
  2. 客户端超时未收到回应,判定连接失败,放弃了这次尝试。
  3. 过了很久,网络恢复通畅,这个滞留的“老僵尸”SYN1 终于慢悠悠送达了服务端。
  4. 如果采用两次握手:服务端一收到 SYN1,立即认为客户端又想建链,回复一个 ACK 并单方面进入 ESTABLISHED 状态,为该连接分配内存缓冲区与资源。
  5. 但客户端早就翻篇了,根本不认这个连接,绝不会给服务端发送任何数据;而服务端却傻傻地一直维持着这个幽灵连接,白白耗死服务器资源!

三次握手的妙处就在于:服务端发回 SYN+ACK 之后,必须等待客户端的最终 ACK 确认。客户端收到服务端针对“老僵尸”的应答时,一查序号发现不是自己当前要的,就会断然发送一个 RST 报文,命令服务端立即释放该连接,绝不会被历史延迟报文愚弄。


4. 四次挥手全时序推演 ​

建立连接只需三次,为什么断开连接却要整整四次? 根本原因在于:TCP 是全双工协议,数据通道是双向独立的! “我不想给你发数据了(我的发送通道关闭)”,并不代表“你也不能给我发数据了(你的发送通道依然可以工作)”。

第一步:主动方请求关闭(FIN) ​

  • 客户端调用 close(),向服务端发送 FIN = 1,ACK = 1,seq=u。
  • 客户端不再发送应用数据,状态从 ESTABLISHED 进入 FIN_WAIT_1。

第二步:被动方确认收到(ACK) ​

  • 服务端收到 FIN,立刻回复 ACK = 1,ack=u+1。
  • 服务端状态变为 CLOSE_WAIT;客户端收到该 ACK 后变为 FIN_WAIT_2。
  • 半关闭状态(Half-Close):此时客户端到服务端的单向通道已关闭,但服务端如果还有没传完的数据,依然可以继续向客户端发送,客户端依然必须接收!

第三步:被动方传完全部数据,发起关闭(FIN) ​

  • 服务端把剩余的最后一点数据全部发完,确认无事可做,也调用 close()。
  • 向客户端发送 FIN = 1,ACK = 1,seq=w。
  • 服务端状态进入 LAST_ACK。

第四步:主动方最终确认(ACK 与 TIME_WAIT) ​

  • 客户端收到服务端的 FIN,回复最终确认 ACK = 1,ack=w+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. 课后核心测验 ​

华为 ICT 大赛 2024–2025 省初赛 · 真题 · 单选

在 TCP 三次握手建立连接的过程中,客户端发送的第一个请求报文(SYN 报文)中,以下哪一项标志位必须被置为 1?

真题衍生 · 判断题 · 单选

在 TCP 四次挥手释放连接的过程中,主动发起关闭的一方在发送完最后一个确认报文(ACK)后,可以立即关闭 Socket 并释放端口资源给新进程使用。

7. 下一步学习 ​

连接建立之后,双方开始通过全双工信道疯狂倾泻数据。

面对可能丢包的脆弱物理介质、速度参差不齐的接收端主机、以及随时可能拥堵瘫痪的互联网骨干网,TCP 究竟是如何施展魔法,把一个不可靠的网络改造成一个绝对可靠、永不丢字、自动调速的钢铁信道的?

下一课,让我们深入攻坚:第 25 课 · TCP 可靠传输:滑动窗口、超时重传与拥塞控制!