每个早高峰都会上演「拥堵悖论」:人人都想快点到,可人人同时踩油门,结果谁也走不动。真正有经验的老司机反而会先慢后快、边开边探路——刚上高速时轻踩油门试探路况,确认路况通畅后逐步提速,遇到前方缓行立刻松油减速,等路通了再慢慢补回来。TCP 的拥塞控制,正是网络世界里的这位「老司机」。它的工作,是帮发送方摸清网络的「路况脾气」,既不一股脑灌爆管道,也不缩手缩脚浪费带宽。本文用「高速公路」这个贯穿全篇的比喻,带你走完一条数据从出发到到达的全过程,看看它在链路上要经历怎样一场「智能交通管制」。
拥塞控制是保证互联网不「堵死」的底层机制,掌握它意味着你具备了排查网络性能问题、优化数据传输效率的硬核能力,是成为资深后端、网络或基础设施工程师的必修课。
可视化演示
展示拥塞窗口AIMD增长与缩减、快重传3次重复ACK机制、以及丢包与延迟信号检测策略对比。
本节配有交互动效(进入视口后自动加载,约 3KB)。
AIMD拥塞窗口演化 慢启动指数增长后线性增长,丢包时减半 cwnd:1→2→4→8→linear AIMD加性增乘性减是经典拥塞控制框架
拥塞的本质
早高峰的高速公路入口匝道,是观察「拥堵悖论」最好的地方:车流一股脑涌入主路,而主路的通行能力是固定的,于是车辆开始减速、排队,队尾越拉越长,最终连匝道口都被堵死。你堵在车流里缓慢挪动时,其实并不是路坏了,而是到达的汽车数量超过了道路的疏散能力。
网络世界完全一样。多个发送方同时往一条瓶颈链路灌数据,速率一旦超过链路的承载上限,路由器里的缓存队列就会像匝道排队一样越积越长。数据包在网络设备里排队,意味着每一跳的转发延迟上升;当队列彻底塞满,后续到达的数据包就会被无情丢弃——这就好比匝道口排队排到马路牙子,交警直接摆手禁止进入,后面的车只能掉头或原地等待。
衡量这种「排队积压」最经典的数学模型是利特尔定律。它用一条极简的公式把队列长度、到达速率和等待时间联系起来:
理解了这条公式,你就明白为什么「网络拥塞」不只是「慢」的问题:它是一连串恶性的连锁反应。发得越多,队列越长,延迟越高,甚至丢包。老司机不会在堵车时猛踩油门——他会收油、控速、与前车保持距离。TCP 的拥塞控制,就是给自己的发送行为装上这样一套「路况感知 + 动态控速」的油门系统。
- L(队列长度)代表路由器缓存中正在排队等待转发的数据包数量,相当于匝道上拥堵的汽车数。
- λ(到达速率)单位时间内到达路由器的新数据包数量,相当于每秒涌进匝道的汽车数量。
- W(平均等待时间)每个数据包从进入队列到被转发出去所消耗的平均时间,相当于每辆车在队里等了多少秒。
- 乘号(三者关系)把到达速率和等待时间相乘,就得到此刻积压在队列里的数据包总数——队伍越壮大,等待越漫长。
滑动窗口机制
高速公路上常有匝道信号灯,每隔几秒放行几辆车进入主路。为什么不定时放行而是按「批」放行?因为主路与匝道之间有一段缓冲带,一次放太多车会把缓冲带塞满,主路上反而乱成一团。TCP 发明了滑动窗口来做同样的事:不让发送方一次把所有数据都扔进网络,而是一次只放行「一个窗口」那么多。
发送方每发一个数据包,都期待对方回一个 ACK(确认)。如果严格「发一个、等一个」,网络传输效率会低到令人发指——高速公路上每辆车都等到前车完全到达才启动,那还要高速公路干什么?滑动窗口允许发送方在收到确认之前,一次性发出窗口大小那么多的数据包。窗口滑动的过程,就像匝道信号灯按批次放行:绿灯亮起,一批车驶入;前面的车确认到达,后面的车再补上。
这里的关键是:发送窗口并不是发送方想定多大就定多大,它受两个「老板」约束。第一个是接收方的接收窗口(rwnd),代表接收方的缓冲区还能装多少数据,相当于目的地停车场还剩多少车位;第二个是拥塞窗口(cwnd),代表发送方对当前网络容量的估算,相当于发送方判断主路还能容纳多少车不堵。真正的发送窗口取两者的较小值:
匝道信号灯的好处是「有序疏导」,滑动窗口的好处也一样:它把一次性的「洪水式发送」拆解成一批批可控的「车队放行」。接下来我们要问:这个 cwnd 到底怎么涨、怎么跌?这就引出了慢启动和拥塞避免这两大核心算法。
- cwnd(拥塞窗口)发送方根据丢包、延迟等信号推测出的网络承载能力,相当于司机凭路况判断主路还能放入多少车。
- rwnd(接收窗口)接收方在 ACK 中通告的剩余接收缓存大小,相当于目的地停车场剩余车位的实时播报。
- min(取小函数)两个限制条件同时生效,取更严格的那个作为最终阈值。
- 发送窗口(最终结果)一次能发出去的数据总量,必须同时满足网络和接收端的双重约束。
慢启动算法
新手司机刚上高速时,绝不会一脚油门踩到底——他先以 60 公里时速试探路况,发现前方通畅,加到 80、再加速到 100……每过一段距离确认安全,就大胆地提一档。TCP 的慢启动(Slow Start)正是这个思路的算法版:它不是真的「慢」,而是「从谨慎起步,然后飞快地试探性加速」。
慢启动的工作方式是这样的:连接刚建立时,cwnd 被设为一个很小的初始值(经典实现是 1 个 MSS,即一个最大段大小;现代 Linux 放宽到 10 个 MSS)。每收到一个 ACK,cwnd 就增加 1 个 MSS。因为一个 RTT(往返时间)内发出的包都能收到 ACK,所以每个 RTT 后 cwnd 会翻一倍:1 → 2 → 4 → 8 → 16……这就是指数增长。
这种增长不可能永远持续。TCP 里预设了一个慢启动阈值(ssthresh),它像高速公路上的限速牌。当 cwnd 小于 ssthresh 时,处于慢启动阶段,窗口指数增长;当 cwnd 达到或超过 ssthresh 时,进入拥塞避免阶段,改用线性增长。数学上,慢启动的增长规律可以写成:
慢启动的「试探性加速」非常契合真实网络:开局时谁都不知道这条链路的水有多深,老老实实从小批量开始,确认一路没丢包,才敢大幅提速。如果中途发生超时丢包,ssthresh 会被设为当前 cwnd 的一半,cwnd 重置回 1,重新走慢启动。这就像新手司机刚把速度提起来,突然发现前方事故亮起刹车灯——立刻松油,重新谨慎起步。可是每次都从 1 重新来,效率太低了。于是 TCP 还有后半招:拥塞避免。
- cwnd(当前拥塞窗口)发送方此刻允许在途的数据包数量,相当于当前允许进入主路的车队规模。
- ×2(每轮翻倍)每个 RTT 内所有 ACK 都到达,说明路况良好,窗口直接加倍。
- cwnd_new(增长后的窗口)新一轮 RTT 中允许发送的数据包数量,翻倍后的车队规模。
- 条件(每经过一个 RTT)翻倍不是瞬间完成的,而是以「往返时间」为节拍器,一个节拍翻一倍。
拥塞避免阶段
老司机把车开到时速 100 公里后,不会继续猛踩油门翻倍提速,因为再快就可能失控。他会怎么做?轻点油门,让车速以「每公里加 1 公里」的节奏缓慢爬升,直到找到一个既稳又快的平衡速度。这就是拥塞避免(Congestion Avoidance)的核心哲学:在接近网络容量上限时,把指数增长切换为小心翼翼的线性试探。
拥塞避免阶段的规则很简单:每经过一个 RTT,cwnd 加 1(单位是 MSS),而不是翻倍。用前面慢启动的数值举例:当 ssthresh = 16 时,cwnd 从 16 开始,每个 RTT 变成 17、18、19……像爬楼梯一样一格一格往上走。这种「每次只多放一辆车进主路」的节奏,极大降低了突然压垮网络的风险。
拥塞避免与乘性减窗合在一起,构成了 TCP 最经典的 AIMD 框架:加法增加(Additive Increase)、乘法减小(Multiplicative Decrease)。「加法增加」是我们刚才说的每 RTT 加 1;「乘法减小」指的是,一旦检测到丢包,ssthresh 立即降为当前 cwnd 的一半,cwnd 也一并削减。增长时如履薄冰,遇险时果断断腕,这是 AIMD 维持网络稳定性的根本逻辑:
而「乘法减小」的数学表达同样关键:一旦出现丢包信号,发送方执行 ssthresh = cwnd / 2。这相当于司机看到前方刹车灯亮起,立刻把车速砍半。注意 AIMD 的妙处:它不要求所有司机都知道路况的全貌,只要每个发送方都遵守「加一减半」的守则,整个系统就能自发地收敛到公平且高效的平衡点。不过,丢包检测也有快慢之分——是傻等超时,还是迅速反应?下一节揭晓。
- cwnd(当前拥塞窗口)此刻允许发送的数据包总量,即当前车队的规模。
- +1(每轮只加一)每个 RTT 只增加 1 个 MSS,相当于车队每轮只多放一辆车进去。
- cwnd_new(增量后的窗口)线性增长后的新窗口值,与慢启动的翻倍形成鲜明对比。
- 条件(每经过一个 RTT)以 RTT 为节拍,每个节拍加一,永不跳档。
快重传快恢复
假如你的车在高速上爆了胎,你是停下车打电话等拖车,还是靠边停车后立刻换上备胎继续走?显然,换备胎继续走远比原地等待高效。TCP 早期处理丢包的方式就是「原地等待」——等超时计时器到期再重传,往往要白白消耗一个 RTT 甚至更多。后来工程师发明了更聪明的办法:快重传(Fast Retransmit)与快恢复(Fast Recovery)。
快重传的原理基于一种「旁车提醒」现象。接收方收到乱序数据包时,会立即重复确认「我还在等第 N 号包」。当发送方连续收到 3 个重复的 ACK 时,基本可以断定第 N 号包在途中丢了——这就像你旁边的司机连续按了三次喇叭提醒你「备胎掉了」。此时发送方不再傻等超时,而是立即重传那个丢失的报文段。
快恢复则是对 ssthresh 的巧妙处置。以前发生丢包后,cwnd 要重置回 1,重新走一遍慢启动,代价高昂。快恢复的思路是:既然还能收到 3 个重复 ACK,说明网络还没完全瘫痪,没必要「停车重启」,把车速降一半继续跑就行。于是发送方把 ssthresh 设为当前 cwnd 的一半,cwnd 设置为新阈值,然后跳到拥塞避免阶段线性增长。整个过程的数学描述十分简洁:
快重传与快恢复是「备胎逻辑」的完美体现:遇到故障,不等待外部救援(超时机制),靠自己的感知(重复 ACK)快速响应,以最小代价恢复行驶。这一机制还有个重要的前提——它依赖接收方的重复 ACK 作为「喇叭提醒」。那么问题来了:发送方凭什么判断网络出问题了?是靠丢包,还是靠延迟?这就进入拥塞信号识别的主题。
- 分母 2(减半)乘法减小策略的标准动作,无论当前 cwnd 多大,一律砍半,相当于车速减半。
- ssthresh(新阈值)丢包后的新慢启动阈值,未来窗口增长到此处就切换为线性模式。
- 等号(直接赋值)快恢复不把 cwnd 打回 1,而是直接设为新阈值,避免重新慢启动的时间开销。
- cwnd(恢复后的窗口)新窗口从阈值处继续按拥塞避免节奏增长,相当于半速巡航后缓缓提速。
拥塞信号识别
判断一个路口堵不堵,老司机有两种办法:一是看到前车刹车灯全亮(丢包信号),二是感觉自己的车速没变但发动机转速越来越高(延迟信号)。TCP 的拥塞检测同样有这两类信号,它们各有各的「脾气」。
丢包信号是经典 TCP 的主流判据。它的逻辑很简单:报文丢了,就说明网络撑不住了。在路由器采用「尾丢弃」(队列满后直接丢包)的时代,丢包确实是拥塞的可靠标志。但它有个致命缺陷:信号是二元的、滞后的。只有当队列完全溢出时才知道网络堵了,好比一定要等到堵死才知道路况不好,此时已经错过了最佳减速时机。更麻烦的是,无线链路、光纤切换等场景会产生「随机丢包」,本来网络不堵,却被误判为拥塞,导致无谓地降低发送速率。
延迟信号则细腻得多。网络延迟可以拆成三个部分:传播延迟(光在光纤里跑的时间)、传输延迟(数据包被推上链路的时间)和排队延迟(在路由器队列里等待的时间)。前两部分基本恒定,只有排队延迟会随拥塞程度连续变化——队列越深,延迟越高。于是 RTT 的升高就成了「发动机转速升高」式的早期预警信号:
除了丢包和延迟,抖动(延迟的方差)也是辅助信号:抖动加剧,说明队列长度在剧烈波动,网络状态不稳定。丢包信号和延迟信号并非互斥,现代拥塞控制往往两者兼用——丢包作为兜底判据,延迟作为早期预测。但这两类信号都属于「被动感知」,就像司机根据刹车灯猜路况。能不能主动去「测量」道路的极限容量,然后照着容量跑?这正是 BBR 算法颠覆性的地方。
- Dp(传播延迟)信号在链路上以光速传播所需时间,由物理距离决定,几乎不变,相当于路本身的长度。
- Dt(传输延迟)把整个数据包按链路速率「推」上线路的时间,由包大小和带宽决定。
- Dq(排队延迟)数据包在路由器队列中等待转发的时长,是拥塞程度最敏感的晴雨表。
- 总和(延迟结构)三者叠加得到端到端总体延迟。Dq 上升意味着队列在积压,网络正在走向拥塞。
BBR新算法
传统 TCP 的思路是「开车看路况,堵了就减速」——永远在试探和退缩之间震荡。而 BBR(Bottleneck Bandwidth and Round-trip propagation time)换了一个完全不同的思路:与其被动应对拥堵,不如直接测量这条高速公路的物理极限——瓶颈路段最多能容纳多少车流?车在通畅状态下往返一趟要多久?测准了这两个数,就按这个「管道容量」稳定巡航,既不塞车也不浪费。
BBR 用两个核心参数描述网络特性。第一个是 BtlBw(瓶颈带宽):整条链路中最窄的那段每秒最多能传输多少数据,相当于高速公路上最窄车道的最大车流通过率;第二个是 RTprop(最小往返传播时间):网络上没有任何排队时,数据包往返一次的最短时间,相当于空无一车时从入口到出口再返回的最快时间。两个参数相乘,就是网络管道的总容量 BDP(带宽延迟积):
BBR 的工作节奏和传统 AIMD 完全不同:它周期性加速探测更高的 BtlBw,再周期性减速探测更低的 RTprop,然后稳定在「刚好填满管道」的发送速率上。相比 TCP 的「锯齿状」窗口曲线——升升升、断了、砍半、再升——BBR 更像一辆开启了自适应巡航的车:平稳行驶,极少急刹。在弱网高丢包场景(如移动网络、长肥链路)中,BBR 通常能跑出数倍于传统算法的吞吐量。不过它并非万能,在公平性、与旧算法的共存方面仍有争议,工程落地时需要谨慎评估。
- BtlBw(瓶颈带宽)链路中最小的一段带宽,决定了整条路的最大数据吞吐率,相当于最窄车道的通行能力。
- RTprop(最小往返时间)无排队状态下数据包往返的最小耗时,相当于空荡荡的高速上跑一趟全程的时间。
- 乘号(容量计算)带宽乘以时间,得到「管道体积」——这条路上最多能同时承载多少比特的数据。
- BDP(带宽延迟积)网络管道容量的量化指标。发送窗口超过 BDP 不会提速,只会把队列撑高。
工程实践调优
一座城市的交通管理,最终要靠交通指挥中心的大屏:传感器汇报实时车流、匝道信号灯自动调节放行速率、可变限速板引导司机减速。网络拥塞控制的工程落地也一样,考验的是「调参、监控、按场景决策」的综合能力。把算法搞清楚只是第一步,真正让 TCP 在业务中跑出高性能的,是实践中的细节。
参数调优是基础。Linux 下我们可以通过 sysctl 调整 TCP 栈的关键参数:net.ipv4.tcp_wmem / tcp_rmem 控制收发缓冲区大小,直接影响窗口上限;net.ipv4.tcp_window_scaling 是否开启决定窗口能否突破 64KB 上限;net.ipv4.tcp_sack 开启选择性确认,避免快重传时不必要的回退;连接初始窗口也可以从默认值调大,减少短连接在慢启动阶段的时间损耗。这就像根据城市路况调整匝道信号灯的绿灯时长和主路限速牌。
监控告警是眼睛。拥塞控制的效果最终要落到四个指标上:丢包率(重传比例升高意味着拥塞信号密集)、RTT(平均延迟与延迟抖动反映队列积压情况)、吞吐量(实际传输速率是否逼近瓶颈带宽)以及队列深度(网络设备缓冲区的占用情况)。用带宽利用率衡量链路是否跑满,是最直观的判断:
部署场景决定算法选型:数据中心内部网络延迟极低、队列极短,适合启用 DCTCP 或带 ECN 的拥塞控制;跨地域长肥链路(带宽大、延迟高)上,BBR 往往能显著提升吞吐量;而实时音视频场景则更看重延迟稳定性,需要配合 FEC 前向纠错等机制。工程化的本质,是理解每个算法在什么路况下才能发挥最佳性能——就像交通指挥中心不会在雨雪天照搬晴天的信号灯方案。搞清楚了算法原理与信号差异,你就能在实际系统中做出有理有据的拥塞控制优化决策。
- 实际吞吐量(分子)单位时间内真正成功传输到对端的数据量,相当于高速公路上实际通过的车流量。
- 瓶颈带宽(分母)链路上最窄处的理论极限速率,相当于道路设计通行能力。
- 除号(利用率计算)实际车流除以设计容量,得到当前道路的繁忙程度。
- U(利用率)0-1 之间的比值。U 高而 RTT 平稳是理想状态;U 高而延迟飙升则是拥塞前兆。
AI 应用场景
数据中心网络性能调优
阶段:模型训练与推理服务的网络基础设施调优。
解决的问题:AI 大模型训练采用分布式并行架构,通信模式呈现典型的「多对一」Incast 流量突发——几十甚至上百台 GPU 服务器同时向一台参数服务器发送数据,瞬间把交换机缓存打满,导致尾延迟飙升、训练效率骤降。传统 TCP 基于丢包的拥塞控制反应慢,等到丢包才降速已经晚了。
具体案例:某云厂商在千卡 GPU 集群中启用 ECN 显式拥塞通知 + DCTCP 拥塞控制算法,让交换机在队列积压早期就打上拥塞标记,发送方据此平滑降速。结果是把参数服务器链路的 P99 延迟从 200ms 降到 20ms,分布式训练的整体吞吐提升了约 30%。
直播推流与 CDN 分发优化
阶段:AI 生成内容的实时分发与媒体传输。
解决的问题:AI 数字人直播、实时渲染视频流对带宽与延迟极其敏感。传统拥塞控制遇到丢包会激进的减半窗口,导致画质频繁波动;而 CDN 面向的用户可能处于弱网环境(Wi-Fi 波动、4G 信号不稳),网络状态远比数据中心复杂。丢包信号在这里失效,延迟信号和带宽估计更有价值。
具体案例:某直播平台在推流链路启用 BBR 拥塞控制后,实测在 5% 随机丢包率的弱网环境下,平均码率提升了 2.1 倍,卡顿率下降 60%。原因正是 BBR 基于带宽估计而非丢包反应,能在高丢包链路中更充分地利用带宽。
移动端 API 网关优化
阶段:智能助手 App 的云端 API 响应优化。
解决的问题:AI 对话类应用对首包延迟极其敏感。移动网络下 TCP 连接建立后的慢启动阶段往往浪费大量 RTT,如果每次请求都从小窗口重新爬升,交互体验会非常差。合理调大初始窗口、开启窗口缩放并配合连接复用,能显著减少启动阶段的浪费。
具体案例:某大模型产品 App 的 API 网关对短连接场景进行调优:将 TCP 初始窗口调整为 10 MSS,开启 SACK 与窗口缩放,同时复用长连接。上线后首包时间平均下降 35%,弱网环境下的请求成功率从 97.2% 提升到 99.1%。
Python 演示
#!/usr/bin/env python3"""TCP 拥塞控制 (AIMD) 模拟: 慢启动、拥塞避免、快恢复""" import random def simulate(loss_rate=0.02, initial_ssthresh=16, rounds=100): cwnd = 1 # 拥塞窗口, 单位 MSS ssthresh = initial_ssthresh history = [] for r in range(rounds): history.append(cwnd) sent = int(cwnd) lost = sum(random.random() < loss_rate for _ in range(sent)) if lost > 0: ssthresh = max(sent // 2, 2) cwnd = ssthresh # 快恢复: 直接降到新阈值 else: if cwnd < ssthresh: cwnd = cwnd * 2 # 慢启动: 指数增长 else: cwnd = cwnd + 1 # 拥塞避免: 线性增长 return history if __name__ == "__main__": hist = simulate() for i, v in enumerate(hist): print(f"RTT {i:3d}: cwnd = {v}")
cwnd = 1初始拥塞窗口设为 1 个 MSS,对应慢启动从最谨慎的试探开始。ssthresh = initial_ssthresh慢启动阈值决定指数增长何时切换为线性增长。lost = sum(random.random() < loss_rate for _ in range(sent))模拟这轮 RTT 里发出的数据包中,有几个因网络拥塞而丢失。ssthresh = max(sent // 2, 2)遇丢包时执行乘法减小,阈值降为当前窗口的一半。cwnd = ssthresh快恢复的策略:不回到 1,而是直接落在新阈值上继续跑。cwnd = cwnd * 2慢启动的指数增长:每轮 RTT 窗口翻倍。cwnd = cwnd + 1拥塞避免的线性增长:每轮 RTT 只加 1 个 MSS。
常见误区
误区我一直以为慢启动就是发送数据很慢,所以 TCP 传输前期效率特别低,应该尽量调大初始窗口来提升速度。
正解慢启动的「慢」是指谨慎地从小窗口起步,但它一旦确认路况良好,就会指数级提速——1、2、4、8、16……几个 RTT 内就能冲到很高的窗口值。它像新手司机先缓慢起步但紧接着就大胆加速,真正意图是「试探性激进」,而非「恒定低速」。盲目的调大初始窗口反而可能在网络状况未知时瞬间造成拥塞。
为何常见:因为名字里带「慢」,初学者容易望文生义;且短连接场景下慢启动确实让传输前期吞吐偏低,很容易让人觉得「慢」是缺陷而不是保护机制。
误区带宽不够就调大发送缓冲区、把拥塞窗口上限调得很高,数据传输就会更快。
正解当窗口超过带宽延迟积(BDP)后,多出来的数据并不会提升吞吐量,而只是填充路由器队列,造成延迟上升、丢包增加。这就像高速公路上车流已经达到道路容量,再多放车进来只会让所有人更慢。正确的做法是让窗口匹配 BDP,而不是无限扩大。
为何常见:直觉上「允许发更多数据 = 传输更快」,但这个直觉忽略了网络中「队列积压」这个中间变量,导致把窗口和吞吐简单画上等号。
误区丢包率一升高,我就断定网络拥堵了,然后大幅度降低发送速率。
正解丢包可能由多种原因造成:无线链路的信号衰减、光纤切换的瞬时中断、路由器缓存策略导致的随机丢弃,这些都与拥塞无关。如果把「随机丢包」误判为「拥塞信号」,就会像司机被路面的小石子颠了一下就猛踩刹车,白白损失吞吐量。这也是 BBR 等基于延迟/带宽估计的算法能胜出的原因——它们不会对所有丢包都作出过度反应。
为何常见:经典 TCP 的教科书把丢包当作拥塞的代名词,初学者形成了强关联;现实中却忽略了网络底层硬件也会产生非拥塞丢包。
误区进入了拥塞避免阶段,网络就不会拥塞了,我可以放心大胆地传输。
正解拥塞避免的本意是「避免拥塞崩溃」——即防止发送速率无限增长把网络彻底压垮,它并没有能力阻止局部拥塞的发生。事实上塞车还是会来的,算法只是用线性增长替代指数增长,让网络在接近容量时更平稳,并在拥塞发生后快速恢复。它不是消除拥堵,而是把拥堵控制在一个可接受的范围。
为何常见:名称中的「避免」二字具有误导性,容易让人把它理解成一种「免疫能力」,而忽略了它只是放缓增长节奏的保守策略。
面试题
Q1. 慢启动阶段,拥塞窗口 cwnd 每经过一个 RTT 通常如何变化?
- A.增加 1 个 MSS
- B.翻倍增长← 正确答案
- C.保持不变
- D.减半
答案:B。慢启动的典型特征是「试探性加速」:每收到一个 ACK,cwnd 加 1 个 MSS,因此在完整的一个 RTT 内窗口整体翻倍。选项 A 是拥塞避免阶段的特征,C 和 D 与慢启动无关。
Q2. 发送方收到几个重复 ACK 时会触发快重传?
- A.1 个
- B.2 个
- C.3 个← 正确答案
- D.4 个
答案:C。TCP 约定收到 3 个重复 ACK 就认为某个报文段丢失,立即重传而不再等待超时。这就像旁边司机连按三次喇叭提醒你,你才确信出问题了。1 个重复 ACK 只是乱序的常见信号,不足以触发重传。
Q3. 拥塞避免阶段,cwnd 每经过一个 RTT 如何变化?
- A.加 1 个 MSS← 正确答案
- B.翻倍
- C.加 10 个 MSS
- D.减半
答案:A。拥塞避免采用「加性增窗」策略,每轮 RTT 只增加 1 个 MSS,对应 AIMD 中的 AI(Additive Increase)。翻倍是慢启动的行为,减半是乘性减窗的行为。
Q4. 以下哪些是经典 TCP 拥塞控制(AIMD 框架)的组成部分?
- A.慢启动← 正确答案
- B.拥塞避免← 正确答案
- C.快重传与快恢复← 正确答案
- D.BBR 带宽探测
答案:ABC。经典 AIMD 框架由慢启动、拥塞避免、快重传和快恢复四个阶段组成,构成完整的窗口演化闭环。BBR 是一种独立于 AIMD 的新算法,不属于经典框架的组成部分。
Q5. 发生丢包时,TCP 的慢启动阈值 ssthresh 会如何变化?
- A.降为当前 cwnd 的一半← 正确答案
- B.降为当前 cwnd 的四分之一
- C.保持不变
- D.翻倍
答案:A。AIMD 的乘性减窗(MD)策略要求 ssthresh 降为 cwnd 的一半。这相当于老司机看到前方刹车灯亮起,立刻把车速砍半。降至四分之一或翻倍都不符合 TCP 标准行为。
Q6. 以下哪些可以作为判断网络拥塞的信号?
- A.数据包丢失← 正确答案
- B.RTT 延迟上升← 正确答案
- C.延迟抖动加剧← 正确答案
- D.接收方通告窗口 rwnd 增大
答案:ABC。丢包信号(丢包)、延迟信号(RTT 上升)和抖动信号(延迟方差加剧)都是拥塞的常见指示。rwnd 是接收方空闲缓存的大小,它与网络拥塞无关,增大 rwnd 表示接收端愿意接收更多数据。
Q7. 为什么基于丢包的拥塞控制算法在光纤链路中可能失效?
- A.光纤链路的带宽太小
- B.光纤存在大量随机丢包且与拥塞无关← 正确答案
- C.光纤链路的 RTT 太长
- D.光纤不支持 TCP 协议
答案:B。光纤切换、信号衰减等会产生少量「随机丢包」,这种丢包与网络拥塞无因果关系。基于丢包的算法会将其误判为拥塞信号,无谓地减半发送速率,导致带宽严重浪费。这正是 BBR 等基于带宽估计的算法在长肥链路上更优的原因。
Q8. BBR 算法主要通过哪两个参数来确定最优发送速率?
- A.cwnd 与 ssthresh
- B.BtlBw 与 RTprop← 正确答案
- C.rwnd 与 MSS
- D.丢包率与重传次数
答案:B。BBR 的核心是测量瓶颈带宽 BtlBw 和最小往返传播时间 RTprop,两者的乘积 BDP 代表网络管道容量。BBR 按 BDP 发送数据,既跑满带宽又不制造排队。cwnd/ssthresh 是传统 AIMD 的参数,BBR 已不再以丢包作为主要反馈。
Q9. 缓冲区膨胀(Bufferbloat)会导致哪些问题?
- A.端到端延迟明显上升← 正确答案
- B.延迟抖动加剧← 正确答案
- C.基于丢包的拥塞控制反应滞后← 正确答案
- D.物理链路带宽自动增大
答案:ABC。路由器缓存过大导致数据包大量排队,延迟和抖动同时飙升;同时因为缓存不易溢出,丢包信号迟迟不来,基于丢包的算法无法及时降速。物理带宽是硬件属性,不会因为缓存增大而改变。
Q10. 发送窗口的最终取值由 cwnd 和 rwnd 中的哪一个决定?
- A.两者中的较大值
- B.两者中的较小值← 正确答案
- C.两者之和
- D.两者之差
答案:B。发送窗口取 min(cwnd, rwnd),两者谁小听谁的。cwnd 受网络容量约束,rwnd 受接收端缓存约束,发送方必须同时满足两者。这就像匝道放行既要看主路余量,也要看目的地停车场是否还有空位。