外观
网络随时在丢包,为什么 TCP 传一千万字能做到一个标点都不差?
在前几课我们反复强调:底层的物理介质存在噪声干扰(BER 误码)、交换机可能在广播风暴中过载、路由器在缓冲区被塞满时会直接粗暴地丢弃 IP 数据包。整个网络底层其实是一个充满未知风险与丢包黑洞的“不可靠信道”。
然而,你在网上下载一个 20GB 的游戏压缩包,解压时却没有损坏哪怕 1 个比特;你在网页里浏览一份法律合同,里面的文字排版丝毫不乱。
这一奇迹的缔造者,正是 传输控制协议(TCP) 强大的可靠传输与调速系统。本课我们将深入 TCP 的机舱腹地,拆解维持其可靠性的三大核心齿轮:字节编号与累计确认、滑动窗口(Sliding Window)流量控制、以及**拥塞控制(Congestion Control)调速网络算法**。
学完本课后,你将能够:
- 掌握 TCP 序号与确认号的字节粒度映射逻辑,明确累计确认(Cumulative ACK)的精确含义。
- 推演 滑动窗口如何根据对端通告的接收窗口(rwnd)动态推移,以及“零窗口探测”如何化解死锁。
- 辨析 超时重传(RTO)与基于 3 个重复 ACK 触发的**快速重传(Fast Retransmit)**的性能差距。
- 背诵并画出 华为 ICT 大赛核心考点——拥塞控制四大经典算法(慢启动、拥塞避免、快重传、快恢复)的拥塞窗口曲线。
1. 编号与累计确认:每一字节都有自己的身份证
许多初学者的第一个直觉误区是:“TCP 序号是第 1 个包编号为 1,第 2 个包编号为 2”。
这是彻底错误的!TCP 是面向字节流(Byte Stream)的协议,序号是按“字节”来逐一计数的!
假设客户端要给服务端发送 3000 个字节的数据:
- 字节编号从 1001 到 4000。
- 如果将数据切成 3 个报文段发送(每个载荷 1000 字节):
- 第 1 个报文:
seq = 1001,包含字节 1001 ~ 2000。 - 第 2 个报文:
seq = 2001,包含字节 2001 ~ 3000。 - 第 3 个报文:
seq = 3001,包含字节 3001 ~ 4000。
- 第 1 个报文:
什么是累计确认(Cumulative ACK)?
当服务端收到了第 1 个报文段(1001 ~ 2000 字节)并校验无误后,服务端向客户端回复一个 ACK 报文:
这个 ack = 2001 的精确科学含义是:
“序号 2000 以及之前的所有字节,我都已经安然无恙、完整无缺地收齐了!请你接下来从第 2001 号字节开始继续往下发。”
累计确认的极大优势在于:极强的抗丢包容错力。即便第 1 个 ACK 在半路不小心丢了,只要第 2 个 ACK(ack = 3001)顺利送达,客户端就知道 3000 以前的全部收到了,无需对第 1 个报文重传!
2. 滑动窗口与流量控制:防止快马踩死小鸡
如果发送方是一台性能极高的万兆服务器,而接收方是一台内存微小的嵌入式物联网设备,如果发送方毫无节制地将千兆数据瞬间泼洒过来,接收方的内存缓冲区会在瞬间被撑爆溢出,导致海量丢包。
这就必须依靠 滑动窗口(Sliding Window)(Sliding Window) 实施的流量控制(Flow Control):
接收窗口(rwnd)与动态滑动
- 接收方在每一次回复的 TCP 首部的
Window字段 中,实时宣告自己当前还剩多少可用缓冲区容量(接收窗口,rwnd,例如rwnd = 65535字节)。 - 发送方严格遵守纪律:在未收到确认前,连续发送的数据总量绝对不能超过对端通告的
rwnd大小! - 随着对端逐步读取缓冲区并回复新 ACK,发送端的窗口就如同在一条无限延展的传送带上不断向右滑动,暴露出新的可以被发送的字节区间。
零窗口死锁与窗口探测(Zero Window Probe)
如果接收端应用程序因为处理缓慢,缓冲区被彻底填满,它会向发送端发一个 Window = 0(零窗口通知)。
- 发送端见状立即挂起,停止发送数据。
- 设想:后来接收端把数据读完了,缓冲区腾出了 10000 字节,它发了一个
Window = 10000的更新包给发送端。 - 但这个通知包在网络中偶然丢了!
- 此时将发生灾难性的死锁(Deadlock):发送端在苦苦等待窗口变大,而接收端在苦苦等待发送端发新数据,双方谁都不动,连接永久卡死!
TCP 的化解方案:持续定时器与零窗口探测包 只要发送端收到零窗口通知,立即启动一个持续定时器(Persistence Timer)。定时器一超时,发送端就会强行向接收端发送一个仅携带 1 个字节载荷的**“零窗口探测报文(Zero Window Probe)”**。接收端收到后被迫回复当前的最新 Window 大小,瞬间打破僵局,化解死锁!
3. 重传机制:超时重传与 3 个 Dup ACK 快速重传
数据在信道中丢了,发送端怎么知道?何时重发?
机制一:超时重传(RTO,Retransmission Timeout)
- 发送端每发一个数据段,都会为它启动一个倒计时定时器。
- 如果在规定的超时时间(RTO)内没有收到对该报文的 ACK,发送端就判定数据包在网络中不幸夭折,立即触发重传。
- RTO 应该设多大?
- 设太大:网络发生丢包后,两端要死等几十秒才重发,延迟巨大,通信极其卡顿;
- 设太小:数据包还在回来的路上(RTT 稍微波动一下),发送端就误以为丢了而频繁瞎重传,严重浪费宝贵的网络带宽。
- 因此现代 TCP 协议采用 Jacobson/Karn 算法,实时动态采样计算链路的往返时延(RTT),让 RTO 始终动态平滑跟踪略大于 RTT 的安全阈值。
机制二:快速重传(Fast Retransmit)
超时重传虽然稳妥,但一旦发生丢包,发送端往往要等一个完整的 RTO 超时周期,性能严重受损。为此,TCP 引入了更为敏锐的快速重传机制(RFC 5681):
设想发送端连续发送了 5 个报文段:1、2、3、4、5。
- 报文 1 顺利抵达:接收端回复
ack = 2(正常确认,期待接收报文 2)。 - 报文 2 在中间路由器丢了!
- 报文 3 顺利抵达:接收端发现失序,缺了报文 2,无法向应用层提交!于是接收端立即回复
ack = 2(第 1 个重复确认,1st Duplicate ACK)。 - 报文 4 顺利抵达:依然缺报文 2,接收端再次回复
ack = 2(第 2 个 Duplicate ACK)。 - 报文 5 顺利抵达:依然缺报文 2,接收端第 3 次回复
ack = 2(第 3 个 Duplicate ACK)。 - 黄金门槛(RFC 5681 核心规则):发送端总共收到了 4 个相同的
ack = 2(1 个原始 ACK + 连续 3 个冗余确认 Dup ACK)!发送端立刻判定:“接收端后面的 3、4、5 都已经安全收到了,唯独把报文 2 丢了!” - 发送端不等 RTO 定时器超时,立即当机立断直接重传报文 2! 整个过程耗时不到半个 RTT,吞吐量几乎不受丢包打断。
4. 拥塞控制:守护全球互联网生命线的四大算法
核心辨析:流量控制 vs 拥塞控制
- 流量控制(Flow Control):属于端到端的微观控制,照顾的是对端单台主机的接收缓冲区,由
rwnd掌控。 - 拥塞控制(Congestion Control):属于全网性的宏观调控,防止过多数据注入网络导致途中的路由器由于队列溢出而发生全局性大拥堵与丢包雪崩,由发送端维护的**拥塞窗口(cwnd)**掌控。
- 发送端的最终实际发送窗口大小受两者共同制约:
为了探测网络的承载极限并不引发崩溃,经典的 TCP Tahoe/Reno 提出了名垂青史的四大拥塞控制算法:
1. 慢启动(Slow Start)
- 刚建立连接时,发送端根本不知道当前网络有多通畅。
- 初始拥塞窗口极小(设为 1 个最大段长度 MSS,现代系统常初始为 10 MSS)。
- 发送端每收到一个 ACK,cwnd 就加 1 MSS;在每个往返时间(RTT)内,cwnd 的数值呈指数级翻倍飞涨:
- 为什么叫“慢启动”?因为它的起点很低(从 1 开始),但启动后的增速是惊人的指数级!
2. 拥塞避免(Congestion Avoidance)
- cwnd 绝不可能无限翻倍下去,否则瞬间就会把网线打爆。
- 协议设置了一个安全警戒门限——慢启动阈值(ssthresh, slow start threshold),例如
。 - 当
时,TCP 立即收敛,切换到“拥塞避免”阶段。 - 在此阶段,增长速度由凶猛的指数翻倍减速为极其平缓的加法线性递增:每个 RTT 仅仅只给 cwnd 增加 1 个 MSS(如
),稳健地一步步试探网络的极限容量。
3. 发生严重丢包(超时重传)时的断崖处置
- 如果 cwnd 一路试探,最终超过了链路瓶颈路由器的承受上限,路由器开始严重丢包,导致发送端触发了超时重传(RTO 溢出):
- TCP 判定网络发生严重塞车,立即采取雷霆手段:
- 将门限阈值直接腰斩:
; - 将拥塞窗口一刀砍到底,直接重置为
! - 重新从 1 开始执行慢启动。这一机制防止了网络发生毁灭性的拥塞崩溃。
- 将门限阈值直接腰斩:
4. 快速恢复(Fast Recovery)
- 如果丢包并不是通过超时感知,而是通过收到 3 个 Dup ACK 感知的,说明网络并没有彻底堵死(因为后面的数据包还能送到并回传 Dup ACK)。
- 此时无需残暴地把 cwnd 砸到 1,而是将
减半后,直接将 设定为新的 附近,直接进入拥塞避免的线性递增阶段,这便是快恢复(Fast Recovery)。
5. 课后核心测验
6. 下一步学习
经过两节高强度的理论洗礼,我们已经彻底搞懂了 TCP 的握手挥手与底层调速哲学。
在工程实践中,面对五花八门的软件需求,工程师该如何在 TCP 与 UDP 之间做出最优雅的架构抉择?在真实网络发生故障时,我们又该如何使用抓包利器 Wireshark 抽丝剥茧捕捉异常的 TCP 报文?
请进入阶段 4 的收官之作:第 26 课 · 传输层协议选型与 Wireshark 抓包实战。