前言
TCP是互联网上使用最广泛的传输层协议。它提供可靠传输、按序交付、流量控制、拥塞控制等能力。其中,滑动窗口和拥塞控制是两个容易被混淆的概念:它们都涉及"窗口",都影响发送速率,但解决的问题完全不同。
这篇文章把这两个机制拆开来讲清楚:滑动窗口解决的是"接收方能不能收得下",拥塞控制解决的是"网络能不能承受得住"。理解这个分工,就理解了TCP为什么能在不可靠的网络上实现可靠传输。
一、为什么需要滑动窗口
1.1 停止等待协议的效率问题
最基础的可靠传输方式是"停止等待":发送方发一个数据包,等待接收方确认(ACK),收到确认后再发下一个。
这种方式逻辑简单,但效率极低。假设发送方和接收方之间的往返时间(RTT)是50毫秒,每个数据包大小为1KB,那么理论最大吞吐量只有:
text
1KB / 0.05s = 20KB/s
链路带宽可能远高于这个数值,但停止等待协议无法充分利用。原因在于:发送方大部分时间都在等待确认,链路处于空闲状态。
1.2 滑动窗口的基本思路
滑动窗口的核心思路是:允许发送方在收到确认之前,连续发送多个数据包。
发送方维护一个"窗口",窗口内的数据可以连续发送而不需要等待确认。每收到一个确认,窗口向前滑动,新的数据可以进入窗口并被发送。
这样,发送方不必每发一个包就停下来等确认,链路的利用率大幅提升。
二、滑动窗口的工作机制
2.1 发送窗口与接收窗口
TCP连接的两端各维护一个窗口:
发送窗口:发送方可以连续发送而不需要等待确认的数据量。窗口大小由接收方通告的接收窗口(rwnd)和拥塞窗口(cwnd)共同决定,取两者中的较小值。
接收窗口:接收方当前能够接收的数据量。接收方通过TCP头部的窗口字段,将自己的接收窗口大小告知发送方。
2.2 窗口的滑动过程
假设发送窗口大小为4个数据段:
发送方连续发送段1、2、3、4
接收方收到段1,返回ACK确认段1
发送方收到ACK后,窗口向前滑动一个位置,段5进入窗口并被发送
接收方收到段2,返回ACK确认段2
发送方窗口再次滑动,段6进入窗口并被发送
这个过程持续进行,窗口不断向前滑动,数据持续发送。
2.3 接收窗口的动态调整
接收窗口的大小不是固定的。接收方的缓冲区可能被应用程序读取数据的速度影响:
如果应用程序读取数据快,接收缓冲区空闲多,接收窗口可以保持较大
如果应用程序读取数据慢,接收缓冲区被占满,接收窗口会减小
如果接收缓冲区完全占满,接收窗口变为0,发送方必须停止发送
当接收窗口变为0时,发送方会启动零窗口探测:定期发送探测报文,询问接收方窗口是否已恢复。这防止了双方陷入永久等待。
2.4 流量控制的作用
滑动窗口实现的流量控制,核心目标是防止发送方发送速度超过接收方的处理能力。接收方通过动态调整窗口大小,控制发送方的发送速率,避免接收缓冲区溢出导致数据丢失。
三、为什么需要拥塞控制
3.1 流量控制解决不了的问题
滑动窗口解决了接收方的处理能力问题,但没有解决网络本身的问题。
如果网络中的路由器或链路出现拥塞,数据包会在路由器队列中排队,延迟增加。当队列满时,路由器会丢弃数据包。发送方检测到丢包后重传,但重传会进一步加重网络负担,形成恶性循环——这就是拥塞崩溃。
流量控制只关注接收方,不关注网络中间状态。如果发送方和接收方之间的网络出现拥塞,即使接收方窗口很大,发送方也不应该继续高速发送。
3.2 拥塞控制的核心思路
拥塞控制的目标是:在没有明确网络状态反馈的情况下,通过探测和调整,找到一个不会导致网络拥塞的发送速率。
TCP拥塞控制通过维护一个拥塞窗口(cwnd)来实现。发送方实际可以发送的数据量,取接收窗口和拥塞窗口中的较小值:
text
实际发送窗口 = min(接收窗口rwnd, 拥塞窗口cwnd)
拥塞窗口由发送方独立维护,根据网络状况动态调整。
四、拥塞控制的四个核心算法
TCP拥塞控制包含四个相互配合的算法:慢启动、拥塞避免、快速重传、快速恢复。
4.1 慢启动
连接建立初期,发送方不知道网络的承载能力,因此从一个较小的拥塞窗口开始探测。
慢启动的规则是:每收到一个ACK,拥塞窗口增加一个MSS(最大报文段长度)。这意味着拥塞窗口随RTT呈指数增长:1、2、4、8、16……
"慢"指的是起点低,而不是增长速度慢。指数增长使发送方能够快速探测到网络的可用带宽。
慢启动设置了一个慢启动阈值(ssthresh)。当拥塞窗口达到ssthresh时,慢启动结束,进入拥塞避免阶段。
4.2 拥塞避免
进入拥塞避免阶段后,拥塞窗口的增长方式从指数变为线性:每个RTT,拥塞窗口增加一个MSS。
线性增长使发送方缓慢逼近网络的承载极限,避免过快增长导致拥塞。这个阶段持续到检测到丢包为止。
4.3 快速重传
当接收方收到乱序的数据段时,会立即发送重复ACK,告知发送方某个数据段未到达。
如果发送方连续收到三个重复ACK,就判断该数据段丢失,立即重传,而不需要等待超时计时器到期。这显著缩短了丢包恢复时间。
4.4 快速恢复
快速重传后,发送方进入快速恢复阶段:
将ssthresh设为当前拥塞窗口的一半
将拥塞窗口设为ssthresh + 3(3表示已收到的三个重复ACK)
每收到一个重复ACK,拥塞窗口增加1
收到新数据的ACK后,拥塞窗口设为ssthresh,进入拥塞避免
快速恢复避免了慢启动的重新开始,使发送方能够更快恢复到丢包前的发送速率。
五、拥塞控制的演进
5.1 TCP Tahoe与TCP Reno
早期的TCP Tahoe在检测到丢包时,无论超时还是快速重传,都会将拥塞窗口重置为1,重新进入慢启动。
TCP Reno引入了快速恢复机制,在快速重传后不重置窗口,而是从ssthresh继续。这是目前大多数操作系统默认支持的版本。
5.2 TCP NewReno
TCP Reno在多个数据包丢失的场景下存在局限。NewReno改进了快速恢复的处理逻辑,能够在一个窗口内多个丢包时更准确地恢复。
5.3 TCP CUBIC
CUBIC是目前Linux系统的默认拥塞控制算法。它使用三次函数代替线性增长,使窗口增长曲线更平滑,在高带宽、长距离网络(高BDP场景)中表现更好。
5.4 BBR
BBR(Bottleneck Bandwidth and Round-trip propagation time)是Google提出的拥塞控制算法。它不依赖丢包作为拥塞信号,而是通过测量瓶颈带宽和最小RTT来构建发送模型。BBR在长距离、高丢包的网络环境中表现突出。
六、滑动窗口与拥塞控制的对比
| 对比维度 | 滑动窗口 | 拥塞控制 |
|---|---|---|
| 解决的问题 | 接收方处理能力 | 网络承载能力 |
| 控制依据 | 接收方通告的接收窗口 | 发送方维护的拥塞窗口 |
| 反馈来源 | 接收方的窗口字段 | ACK、丢包、RTT变化 |
| 调整方向 | 接收方主动调整 | 发送方主动探测 |
| 目标 | 防止接收缓冲区溢出 | 防止网络拥塞崩溃 |
| 实际发送窗口 | min(rwnd, cwnd) | min(rwnd, cwnd) |
两者协同工作:滑动窗口确保发送方不超过接收方的处理能力,拥塞控制确保发送方不超过网络的承载能力。实际发送窗口取两者中的较小值,任何一方成为瓶颈时,发送速率都会被限制。
七、总结
TCP的可靠传输依赖两个核心机制:
滑动窗口实现了流量控制。发送方可以在收到确认前连续发送多个数据段,窗口大小由接收方动态调整,防止接收缓冲区溢出。
拥塞控制实现了网络稳定性。发送方通过慢启动、拥塞避免、快速重传、快速恢复等算法,动态调整拥塞窗口,探测网络的可用带宽,避免因发送速率过快导致网络拥塞崩溃。
两者的关系可以概括为:滑动窗口管"接收方能不能收",拥塞控制管"网络能不能扛"。实际发送窗口取两者中的较小值,任何一方出现瓶颈,发送速率都会相应调整。理解这个分工,就理解了TCP为什么能在不可靠的网络上实现可靠传输。