在快递公司当调度员的老唐,每天要给客户阿诚发几百件包裹。每发出几件,阿诚都会回一张“签收回执”;可要是某件包裹半路丢了,阿诚那边就会一直缺货。老唐怎么知道包裹丢了?等多久才能补发?补发之后发货节奏怎么调整?这套“包裹救援”的打法,跟 TCP 协议在网络上保障数据可靠到达的逻辑一模一样。本文要讲的 TCP 重传与快速恢复,就是老唐面对丢包时的整套应急预案:等多久算丢(超时重传)、如何提前发现丢了(快速重传)、补发后怎样调整发货节奏(快速恢复)、以及如何用一张“缺货清单”精准补救(SACK)。把这一套吃透,你就掌握了 TCP 可靠性传输的核心心法。

岗位关联

理解 TCP 的重传与恢复机制,是排查网络性能问题、优化传输吞吐量的基本功——面试必考、调优必备。

目录
  1. 可视化演示
  2. 超时重传机制
  3. 快速重传机制
  4. 快速恢复机制
  5. 拥塞窗口调控
  6. 重传事件公式
  7. RTO平滑估计
  8. SACK选择性确认
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

交互式展示TCP超时重传与快速重传/恢复两条可靠性路径:超时模式下RTO定时器超触发重传并cwnd归1,快速模式下3个冗余ACK触发快重传并cwnd减半,切换模式对比窗口调整策略。

本节配有交互动效(进入视口后自动加载,约 3KB)。

超时重传机制 发送方为每个报文段启动RTO定时器 超时未收到ACK则重传,cwnd降为1进入慢启动 RTO = SRTT + 4\u00d7RTTVAR RTO由平滑RTT动态估计

超时重传机制

老唐给阿诚发包裹后,会默默在心里掐一个秒表:正常情况下一来一回大概需要多久?如果秒表都走完一圈了还没收到回执,老唐就会断定“这单肯定丢了”,于是重新发一份。这个“秒表走一圈”的等待上限,在 TCP 里就是重传超时时间(RTO,Retransmission Timeout),而那只“秒表”则是重传计时器。它是 TCP 可靠性的最后一道保险——不管网络状况多复杂,只要超时了,就一定重传。

RTO 的值不能拍脑袋定:定太短,网络稍微慢一点就误判为丢包,造成大量重复发送;定太长,真的丢了包要干等很久,吞吐量直线下降。所以 TCP 用一套平滑算法动态估计 RTT(报文往返时间),再叠加一个安全余量,得出 RTO。

超时之后,TCP 还会采取指数退避策略:第一次超时重传后,如果再次超时,RTO 直接翻倍(2 倍、4 倍、8 倍……)。这就像老唐发现补发的包裹又丢了,会怀疑“是不是路况全面恶化了”,于是下次再多等一会儿才下结论,避免在瘫痪的网络上疯狂重发。与此同时,超时重传还意味着网络可能严重拥塞,拥塞窗口会直接降为 1 个 MSS,相当于老唐决定“先只发一件试试水”。

RTO=SRTT+4×RTTVAR\text{RTO} = \text{SRTT} + 4 \times \text{RTTVAR}
RTO 等于“平均往返时间”加上“4 倍的往返波动幅度”,相当于在老唐估计的快递往返耗时上,留出足够的缓冲余量。
  1. SRTT(平滑往返时间)对历史 RTT 样本做加权平均得到的稳定估值,相当于老唐统计过去一百趟快递的平均耗时。
    SRTT=(1−α)×SRTT旧+α×RTT样本\text{SRTT} = (1-\alpha) \times \text{SRTT}_{\text{旧}} + \alpha \times \text{RTT}_{\text{样本}}
  2. RTTVAR(往返时间偏差)衡量 RTT 的波动大小,相当于“这一趟比平均快还是慢、平均差多少”。
    RTTVAR=(1−β)×RTTVAR旧+β×∣SRTT−RTT样本∣\text{RTTVAR} = (1-\beta) \times \text{RTTVAR}_{\text{旧}} + \beta \times |\text{SRTT} - \text{RTT}_{\text{样本}}|
  3. 4 倍偏差做余量把波动幅度放大 4 倍加在均值上,留足安全余量,避免网络抖动引发误判。
    RTO=SRTT+4×RTTVAR\text{RTO} = \text{SRTT} + 4 \times \text{RTTVAR}

快速重传机制

超时重传虽然可靠,但代价是“干等”。老唐很快发现了一个更聪明的办法:如果阿诚收到包裹 1、2、4、5,发现 3 丢了,他回执时不会干等 3,而是继续确认“最后收到的连续包裹是 2”,然后每次再收到新包裹都重复发一张“我还在等 3 号”的回执。连续三张相同的“等 3 号”回执飘回老唐手上时,老唐几乎可以断定 3 号真的丢了——于是不等秒表走完,立刻补发 3 号。

这就是冗余 ACK(Duplicate ACK) 的原理:接收方每收到一个乱序到达的报文段,就重发一次对“期望收到的下一个序号”的确认。当发送方收到 3 个相同的冗余 ACK 时,触发快速重传(Fast Retransmit)——它最大的价值是提前感知丢包,不用等 RTO 计时器到期。

快速重传的精髓在于:用对方的“催促声”代替自己的“秒表”。Timer 还没响,但阿诚已经喊了三遍缺货,老唐还等什么?马上补发!这条路径比超时重传快得多,因此成为 TCP 应对单包丢失的首选救援方案。

重复ACK次数≥3⇒立即重传丢失报文段\text{重复ACK次数} \geq 3 \Rightarrow \text{立即重传丢失报文段}
三个重复的 ACK,就像阿诚连喊三遍“缺 3 号包裹”——喊一遍可能是路上堵了一下,喊三遍基本可以确定是真丢了。
  1. 冗余 ACK 的产生接收方收到比期望序号更大的报文段时,不能确认这个乱序段,只能重复确认“最后一段连续数据”。
    ACK重复=期望收到的下一个序号\text{ACK}_{\text{重复}} = \text{期望收到的下一个序号}
  2. 三次重复的意义收到 1 个冗余 ACK 可能只是包乱序(后发的先到),但连续 3 个说明后续报文都到了、唯独缺这一个,丢包概率极高。
    dupACKCount=3⇒判定丢包\text{dupACKCount} = 3 \Rightarrow \text{判定丢包}
  3. 立即重传触发条件满足后发送方马上重发丢失段,不再等待 RTO,显著缩短了丢包恢复时间。
    重传等待时间=0\text{重传等待时间} = 0

快速恢复机制

补发包裹之后,老唐面临一个抉择:发货节奏要不要调整?如果调整为“一件一件发”(回到慢启动),虽然稳妥但效率太低;如果完全不停,又怕网络真的堵了。快速重传给出的答案是:将发货上限砍一半,但保持连续发货,绝不停摆——这就是快速恢复(Fast Recovery)。

快速恢复的核心逻辑:3 个冗余 ACK 不仅说明丢了 1 个包,同时还说明有 3 个报文段已经成功到达对端。既然网络还能“消化”这些数据,说明拥塞程度并没有严重到需要从头再来。于是 TCP 将拥塞窗口从“减半后的阈值”再往上加 3 个 MSS,相当于承认那 3 个已成功离网的报文段占用的容量。

对比一下:超时重传后 cwnd 归 1,等于老唐“只骑一辆三轮车送货”;而快速恢复后 cwnd 只降到原来一半附近,等于老唐“把卡车换成小货车,但照样跑”。这就是“快恢复”里“快”字的含义——恢复速度快,绝不推倒重来。

cwnd新=ssthresh+3×MSScwnd_{\text{新}} = \text{ssthresh} + 3 \times \text{MSS}
快速恢复的窗口 = 减半后的阈值 + 3 个 MSS。那 3 个 MSS,正是对应着 3 个已成功到达对方的报文段。
  1. 更新 ssthresh先把慢启动阈值设为当前窗口的一半,并保证至少 2 个 MSS。
    ssthresh=max⁡(cwnd旧2, 2×MSS)\text{ssthresh} = \max\left(\frac{cwnd_{\text{旧}}}{2},\ 2 \times \text{MSS}\right)
  2. 新 cwnd 等于阈值加 3在减半后的基础上加回 3 个 MSS,因为这 3 个段已被确认离开网络。
    cwnd新=ssthresh+3×MSScwnd_{\text{新}} = \text{ssthresh} + 3 \times \text{MSS}
  3. 进入拥塞避免此后每收到一个冗余 ACK,cwnd 临时再增加 1 个 MSS,直到重传的包被确认,再回落到 ssthresh 继续线性增长——整个过程不会回到慢启动。
    cwnd=ssthresh(重传确认后)cwnd = \text{ssthresh} \quad (\text{重传确认后})

拥塞窗口调控

老唐的仓库门口挂着一块白板,上面写着“当前最多同时在路上的包裹数”,这就是 TCP 的拥塞窗口(cwnd,Congestion Window)。它决定了发送方一次能往网络里“塞”多少数据而不等确认。窗口太小浪费带宽,窗口太大会把网络堵死。TCP 用一套分阶段的调控策略,让窗口大小既激进又稳妥。

调控分两个阶段,分水岭是慢启动阈值 ssthresh:

慢启动的“快”和拥塞避免的“稳”,配合 ssthresh 的自动降档,构成了 TCP 拥塞控制的骨架。老唐就是靠着白板上这个动态数字,在网络这个“路况未知”的环境里找到了“既快又不堵”的发货节奏。

cwnd={cwnd×2,cwnd<ssthresh(慢启动)cwnd+1,cwnd≥ssthresh(拥塞避免)cwnd = \begin{cases} cwnd \times 2, & cwnd < \text{ssthresh} \quad (\text{慢启动}) \\ cwnd + 1, & cwnd \geq \text{ssthresh} \quad (\text{拥塞避免}) \end{cases}
窗口小于阈值时每轮翻倍(慢启动,试探性狂奔),达到阈值后每轮只加 1(拥塞避免,谨慎前行)——就像老唐先小批量试路,确认路况良好后逐步放大发货量,等量大了就改为每次多增一辆车。
  1. 慢启动阶段cwnd 从 1 个 MSS 开始,每收到一个 ACK 就翻倍增长,对应老唐“这趟跑通了,下趟发两倍的货”。
    cwnd新=cwnd旧×2cwnd_{\text{新}} = cwnd_{\text{旧}} \times 2
  2. 到达阈值后转入拥塞避免当 cwnd ≥ ssthresh,增速降为每轮 +1,避免线性拥堵,对应老唐“量大了,每次只多加一辆车”。
    cwnd新=cwnd旧+1cwnd_{\text{新}} = cwnd_{\text{旧}} + 1
  3. 丢包触发阈值更新无论超时还是快重传,ssthresh 都会被降到当前 cwnd 的一半(有下限 2 MSS),作为下一轮“激进转谨慎”的提前刹车点。
    ssthresh=max⁡(cwnd2, 2×MSS)\text{ssthresh} = \max\left(\frac{cwnd}{2},\ 2 \times \text{MSS}\right)

重传事件公式

前面几节分别讲了不同场景的窗口调整规则,这一节把它们浓缩成一张“公式速查表”。当丢包事件发生时,TCP 会先统一更新 ssthresh,再根据事件类型决定 cwnd 的取值。这就像一个标准化的应急预案:不管遇到什么事故,老唐先做同一件事(把发货上限砍一半),然后再按事故等级决定是“只发一件”还是“降到半速继续跑”。

这套公式的巧妙之处在于用同一个动作(减半阈值)衔接两种不同程度的响应。超时是“红色警报”——全面收手重新试探;快重传是“黄色预警”——降速但不停工。老唐根据警报颜色的不同,决定是骑三轮车还是开小货车,但不管哪种情况,都会把“白板上的目标上限”先减半再说。

ssthresh=max⁡(cwnd旧2, 2×MSS)\text{ssthresh} = \max\left(\frac{cwnd_{\text{旧}}}{2},\ 2 \times \text{MSS}\right)
cwnd新={1×MSS,超时重传ssthresh+3×MSS,快速恢复cwnd_{\text{新}} = \begin{cases} 1 \times \text{MSS}, & \text{超时重传} \\ \text{ssthresh} + 3 \times \text{MSS}, & \text{快速恢复} \end{cases}
两条路线的总开关都是“窗口减半”:超时后从 1 开始慢启动,快恢复则从“减半再加三”出发继续走拥塞避免。
  1. 统一更新阈值无论哪种重传,先把 ssthresh 降到旧窗口的一半,并设下限 2 个 MSS,防止阈值小到没法用。
    ssthresh=max⁡(cwnd旧2, 2×MSS)\text{ssthresh} = \max\left(\frac{cwnd_{\text{旧}}}{2},\ 2 \times \text{MSS}\right)
  2. 超时分支超时意味着网络可能严重拥塞甚至断路,cwnd 直接置为 1 个 MSS,从头开始慢启动试探。
    cwnd新=1×MSScwnd_{\text{新}} = 1 \times \text{MSS}
  3. 快恢复分支冗余 ACK 证明链路仍能通行,cwnd 只需降到减半后的阈值再加 3,然后继续拥塞避免。
    cwnd新=ssthresh+3×MSScwnd_{\text{新}} = \text{ssthresh} + 3 \times \text{MSS}

RTO平滑估计

老唐的手账本里记录着每一趟快递的往返耗时。但他不会拿最新一趟的数据直接当“标准耗时”,因为单次数据可能被红绿灯、堵车等偶然因素带偏。他用的方法是:新估算 = 大部分旧记忆 + 小部分新样本。这个思路落到 TCP 里就是 SRTT(平滑往返时间) 的递推更新。

由于网络波动是常态,TCP 还维护了一个偏差估计 RTTVAR,记录“实际往返时间和平均值的偏离程度”。两者结合,就是我们在第一节见过的 RTO 公式:

α 取 0.125、β 取 0.25 是 RFC 6298 的标准参数。它们决定了 RTO 对网络变化的反应速度:α 越小,SRTT 越平滑但反应越迟钝;α 越大,反应越快但越容易被单次抖动带偏。老唐选择“每次只信新样本一点点”,正是为了在“快速适应”和“稳定不飘”之间取得平衡。

SRTT=(1−α)×SRTT+α×RTT样本\text{SRTT} = (1-\alpha) \times \text{SRTT} + \alpha \times \text{RTT}_{\text{样本}}
RTO=SRTT+4×RTTVAR\text{RTO} = \text{SRTT} + 4 \times \text{RTTVAR}
RTO 的核心思想:用加权平均消除偶然波动(SRTT),用偏差统计量化稳定性(RTTVAR),再留足余量。相当于老唐不仅看平均耗时,还看“这趟路到底有多不靠谱”。
  1. SRTT 递推更新新 SRTT 由 87.5% 的旧值和 12.5% 的新样本加权合成,α 取 0.125 表示“每次只相信新样本一点点”。
    SRTT新=(1−0.125)×SRTT旧+0.125×RTT样本\text{SRTT}_{\text{新}} = (1 - 0.125) \times \text{SRTT}_{\text{旧}} + 0.125 \times \text{RTT}_{\text{样本}}
  2. RTTVAR 递推更新偏差统计同样采用指数加权,β 取 0.25,衡量“这次偏离平均值的程度”,每次吸收 25% 的新偏差信息。
    RTTVAR新=(1−0.25)×RTTVAR旧+0.25×∣SRTT−RTT样本∣\text{RTTVAR}_{\text{新}} = (1 - 0.25) \times \text{RTTVAR}_{\text{旧}} + 0.25 \times |\text{SRTT} - \text{RTT}_{\text{样本}}|
  3. 合成 RTO为了对抗突发抖动,RTO 至少等于 SRTT 加 4 倍偏差;同时 RFC 规定 RTO 下限不低于 1 秒(早年实现),避免频繁伪重传。
    RTO=SRTT+4×RTTVAR\text{RTO} = \text{SRTT} + 4 \times \text{RTTVAR}

SACK选择性确认

当网络一次丢了多个包裹时,老唐的麻烦来了:快速重传只能“猜”出第一个丢的包,剩下的包还要靠后续的冗余 ACK 一个个去猜、一个个去补。更糟的是,如果只靠累积 ACK,接收方甚至无法告诉发送方“我其实已经收到了 5 号和 7 号,就差 6 号”。这时 TCP 祭出SACK(Selective Acknowledgment,选择性确认):让接收方在 ACK 里附上一张“缺货清单”,精确列出哪些包裹已到、哪些还缺。

SACK 之所以重要,是因为它把“快速重传”从“猜一个补一个”升级为“看一眼全补”。配合快速重传使用,SACK 能显著提升多包丢失场景下的恢复效率:老唐不再需要等阿诚喊三遍才知道第一件丢的是 3 号,而是看着缺货清单一次性把 3 号、6 号、9 号全部补发完——这在无线网络、高丢包链路上效果尤其明显。

SACK选项={[L1,R1], [L2,R2], …, [Ln,Rn]}\text{SACK选项} = \left\{ [L_1, R_1],\ [L_2, R_2],\ \ldots,\ [L_n, R_n] \right\}
SACK 的每个“确认块”用一对序号区间 [左边界,右边界] 表示“这段数据我已收到”。老唐拿到清单,一次就能知道所有缺失段,而不是靠猜。
  1. 确认块结构每个块是一个左闭右开的序号区间,标记一段已连续收到的数据范围。
    [Li,Ri]=已收到的第 i 段连续数据的起止序号[L_i, R_i] = \text{已收到的第 } i \text{ 段连续数据的起止序号}
  2. 多包丢失识别接收方把所有已收段全部罗列出来,发送方对比自己的发送记录,即可算出所有丢失段的位置。
    丢失段=已发送序号∖⋃i=1n[Li,Ri]\text{丢失段} = \text{已发送序号} \setminus \bigcup_{i=1}^{n} [L_i, R_i]
  3. 一次重传多个缺失段得到完整丢失列表后,发送方不再受“每次只重传一个段”的限制,可在一个 RTT 内补发所有缺口。
    重传轮次=1(覆盖所有SACK指示的缺失段)\text{重传轮次} = 1 \quad (\text{覆盖所有} SACK \text{指示的缺失段})

AI 应用场景

直播推流链路优化

阶段:网络传输优化

解决的问题:视频直播推流依赖 TCP 传输,主播网络抖动导致丢包时,传统 RTO 设置过长会造成画面卡顿数秒。通过调小 RTO 下限、启用 SACK 和快速重传,能让丢包后 1-2 个 RTT 内恢复传输,显著降低卡顿率。

具体案例:某直播平台的推流 SDK 在弱网测试中发现,启用 SACK 后多包丢失场景的重传效率提升约 40%,配合快重传参数调优,主播上行丢包 5% 时观众端卡顿时长从 4.8 秒降至 1.2 秒。

游戏服务器指令同步

阶段:实时通信优化

解决的问题:多人竞技游戏中,玩家操作指令经 TCP 传输时若发生丢包,超时重传的长等待会导致“瞬移”和操作回滚。网络层开启快速重传与快速恢复,让服务器在 3 个冗余 ACK 触发下即时补发指令包,保障操作流畅。

具体案例:某 MOBA 游戏开发团队在跨区对战场景中发现,默认 Linux TCP 栈未开启 SACK,跨洋链路 1% 丢包率下重传延迟约 200ms;开启 SACK 并将 RTO 最小下限调至 200ms 后,指令恢复时间缩短到 60ms 以内。

数据中心大流量吞吐调优

阶段:网络运维与容量规划

解决的问题:数据中心内部 AI 训练任务涉及海量参数同步,TCP 丢包会导致训练任务停滞。通过观察 cwnd 曲线和重传统计,运维人员可定位是超时重传过多(说明拥塞严重)还是快速恢复频繁(说明偶发丢包),从而针对性调整网络设备缓冲和流控参数。

具体案例:某云厂商在训练集群扩容时发现,分布式同步的 all-reduce 流量因尾部丢包触发大量超时重传,吞吐量下降 30%;调整交换机 ECN 阈值并结合快重传参数后,有效吞吐恢复至理论值的 92%。

Python 演示

python43 行
"""TCP 超时重传与快速恢复模拟"""import random MSS = 1SRTT = 100.0RTTVAR = 50.0ALPHA = 0.125BETA = 0.25  def update_rto(rtt_sample):    global SRTT, RTTVAR    SRTT = (1 - ALPHA) * SRTT + ALPHA * rtt_sample    RTTVAR = (1 - BETA) * RTTVAR + BETA * abs(SRTT - rtt_sample)    return SRTT + 4 * RTTVAR  def after_loss(cwnd, kind):    ssthresh = max(cwnd // 2, 2 * MSS)    if kind == "timeout":        return MSS, ssthresh    return ssthresh + 3 * MSS, ssthresh  def run():    cwnd, ssthresh = MSS, 16    events = (["normal"] * 4 + ["timeout"] + ["normal"] * 3 +              ["fast"] + ["normal"] * 3 + ["fast"] + ["normal"] * 3)    for i, ev in enumerate(events, 1):        rtt = random.uniform(80, 120)        rto = update_rto(rtt)        if ev == "normal":            if cwnd < ssthresh:                cwnd *= 2            else:                cwnd += 1        else:            cwnd, ssthresh = after_loss(cwnd, ev)        print("第%2d轮 %-4s cwnd=%-3d ssthresh=%-3d RTO=%.1fms" % (            i, ev, cwnd, ssthresh, rto))  run()
目的:模拟 TCP 发送方在正常传输、超时重传和快速恢复三种事件下,拥塞窗口 cwnd、慢启动阈值 ssthresh 与 RTO 的联动变化,直观展示两种丢包恢复路径的差异。
  1. def update_rto(rtt_sample):实现 RTO 平滑估计,每来一个新 RTT 样本就更新 SRTT 与 RTTVAR,再合成 RTO。
  2. SRTT = (1 - ALPHA) * SRTT + ALPHA * rtt_sample对应正文的 SRTT 递推公式,α=0.125 表示只吸收 12.5% 的新样本。
  3. RTTVAR = (1 - BETA) * RTTVAR + BETA * abs(SRTT - rtt_sample)偏差统计用 β=0.25,量化往返时间的波动程度。
  4. def after_loss(cwnd, kind):丢包后的统一处理入口,先算 ssthresh 再按事件类型分支。
  5. ssthresh = max(cwnd // 2, 2 * MSS)阈值减半且有 2 个 MSS 的下限,对应正文公式。
  6. return ssthresh + 3 * MSS, ssthresh快速恢复分支:新 cwnd 等于“减半阈值加 3”,不回到慢启动。
  7. if cwnd < ssthresh: cwnd *= 2 else: cwnd += 1慢启动翻倍、拥塞避免加一的窗口调控规则。
  8. events = ...预设事件序列,在"normal"中插入一次 timeout 和两次 fast,模拟两种丢包场景对比。

常见误区

误区我以为是网络丢包了,赶紧重传。但有时候其实是网络路径变了,导致包乱序到达,接收方发冗余 ACK 只是因为顺序不对,并不是真的丢了。

正解3 个冗余 ACK 是“大概率丢包”的强信号,但不是绝对证据。包乱序、重复投递都可能产生冗余 ACK。所以 TCP 校验了重复 ACK 携带的序号是否在已发送但未确认的窗口内,若序号不合法则忽略。业界还有“DSACK“(带 SACK 的重复确认)机制,专门告诉发送方”你重传的包可能白传了”。

为何常见:资料里常简化说“三次冗余 ACK 触发重传”,初学者容易忽略“为什么是三次”背后的概率考量。

误区反正都是把丢了的包重新发一遍,区别不大,性能应该差不多吧。

正解两者差别巨大。超时重传要等 RTO 到期,等待期可能长达数百毫秒甚至数秒,且触发后 cwnd 归 1 重新慢启动;快速重传在收到 3 个冗余 ACK 的瞬间就重传,等待时间几乎为零,且配合快速恢复 cwnd 只降一半。一个是“最后保险”,一个是“主动救援”,恢复代价完全不同。

为何常见:从用户视角看两者都表现为“数据最终到了”,容易忽视底层窗口调整的巨大差异。

误区RTO 越小,丢包发现得越快,重传越及时,用户体验应该更好。

正解RTO 太小会导致“伪重传”:网络只是稍微慢了一点,发送方就误判丢包并重传,白白浪费带宽、加重拥塞。RFC 6298 规定 RTO 下限为 1 秒(后续实现中可调小但需谨慎),正是为了防止这种“过敏反应”。就像老唐不能在快递刚晚点两分钟就断定包裹丢了——那样只会让路上的包裹更多、路更堵。

为何常见:直觉上“反应快 = 好”,但忽略了网络环境的动态性和误判的代价。

误区丢包之后 TCP 都要从头慢启动,快速恢复只是换了个名字。

正解恰恰相反,快速恢复的设计初衷就是避免回到慢启动。它只把 cwnd 降到 ssthresh(减半后的阈值)再补 3 个 MSS,然后继续在拥塞避免阶段线性增长;只有超时重传才回慢启动。两者是截然不同的“降速策略”——一个是切换档位继续跑,一个是靠边停车重新起步。

为何常见:把“快速重传”和“快速恢复”打包记成“丢包了就重来”,忽略了快恢复的“半降窗”独特价值。

误区有了 SACK 能精确知道哪些包丢了,那冗余 ACK 和快速重传还有啥用?删掉算了。

正解SACK 和快速重传是协作关系,不是替代关系。快重传是“触发机制”(谁先喊缺货),SACK 是“信息增强”(把缺货清单列全)。没有快重传,SACK 接收方仍可发冗余 ACK,但发送方缺乏明确的触发条件及时行动;没有 SACK,快重传在多包丢失时只能一个个猜。两者配合,才能实现“快速定位 + 完整补发”。

为何常见:人们容易认为“更先进的机制会淘汰旧机制”,但网络协议中很多机制是叠加演进而非互相取代。

面试题

Q1. TCP 快速重传的触发条件是发送方收到几个相同的重复 ACK?

单选题 · easy

  • A.1 个
  • B.2 个
  • C.3 个← 正确答案
  • D.4 个

答案:C。收到 3 个冗余 ACK 才触发快速重传,这是 RFC 的标准阈值。1 个或 2 个冗余 ACK 可能只是包乱序导致的,不足以判定丢包。就像阿诚喊一遍“缺货”可能是路况问题,喊三遍才是真的丢了。

Q2. TCP 发生超时重传后,拥塞窗口 cwnd 的值被设置为多少?

单选题 · easy

  • A.1 个 MSS← 正确答案
  • B.当前 ssthresh
  • C.ssthresh + 3 个 MSS
  • D.当前 cwnd 的一半

答案:A。超时重传意味着网络可能严重拥塞,TCP 将 cwnd 直接降为 1 个 MSS,从头开始慢启动试探。这好比老唐把卡车换成三轮车,先探探路况再说。

Q3. 快速恢复之后,新的拥塞窗口 cwnd 值为多少?

单选题 · mid

  • A.1 个 MSS
  • B.当前 ssthresh
  • C.ssthresh + 3 个 MSS← 正确答案
  • D.当前 cwnd 的一半

答案:C。快恢复中 cwnd = ssthresh + 3×MSS。ssthresh 先降为旧 cwnd 的一半,再加回 3 个 MSS——这 3 个 MSS 对应已成功到达对端的 3 个报文段。注意 A 选项是超时重传的处置,不要混淆。

Q4. 快速恢复机制中,ssthresh 的更新公式是什么?

单选题 · mid

  • A.ssthresh = cwnd
  • B.ssthresh = max(cwnd / 2, 2 × MSS)← 正确答案
  • C.ssthresh = cwnd / 4
  • D.ssthresh = MSS

答案:B。无论超时还是快恢复,ssthresh 统一更新为 max(cwnd/2, 2×MSS)。下限 2 个 MSS 是为了防止阈值过小导致窗口永远卡在慢启动阶段。

Q5. 以下哪些情况会触发 TCP 重传?

多选题 · mid

  • A.RTO 超时← 正确答案
  • B.收到 3 个冗余 ACK← 正确答案
  • C.SACK 选项指示有报文段丢失← 正确答案
  • D.收到接收窗口为 0 的报文

答案:ABC。RTO 超时触发超时重传,3 个冗余 ACK 触发快速重传,SACK 告知缺失段后发送方也会针对性重传。D 选项的零窗口只是暂停发送,并不触发重传——那是流量控制而非丢包恢复。

Q6. 与超时重传相比,快速重传+快速恢复的优势有哪些?

多选题 · mid

  • A.无需等待计时器到期,恢复更及时← 正确答案
  • B.cwnd 只降一半而非归 1,吞吐量损失小← 正确答案
  • C.能识别出所有类型的丢包
  • D.不需要接收方发送冗余 ACK

答案:AB。快重传用冗余 ACK 提前感知丢包,不必等 RTO;快恢复将 cwnd 降为 ssthresh+3 而非归 1。但快重传只针对单包丢失高效,多包丢失需配合 SACK;且它依赖接收方发冗余 ACK,D 说反了。

Q7. 关于 SACK 机制,以下说法正确的有哪些?

多选题 · hard

  • A.接收方用 SACK 块精确告知发送方哪些数据段已收到← 正确答案
  • B.SACK 能显著提升多包丢失场景的重传效率← 正确答案
  • C.SACK 可以完全取代快速重传
  • D.SACK 选项携带在 TCP 报文头部← 正确答案

答案:ABD。SACK 的确认块 [L,R] 精确标记已收区间,多包丢失时一次就能补发全部缺口。但它是快速重传的增强而非替代——快重传负责触发、SACK 负责信息,两者配合才高效。SACK 信息确实携带在 TCP 选项字段中。

Q8. RTO(重传超时时间)的标准估计公式是?

单选题 · hard

  • A.RTO = SRTT
  • B.RTO = SRTT + 4 × RTTVAR← 正确答案
  • C.RTO = RTTVAR + 4 × SRTT
  • D.RTO = SRTT + 2 × RTTVAR

答案:B。RFC 6298 规定 RTO = SRTT + 4×RTTVAR。SRTT 是平滑的往返时间均值,RTTVAR 是往返时间的偏差估计,4 倍偏差为安全余量。切忌把系数张冠李戴——均值占主导、偏差做缓冲。

Q9. SRTT 平滑更新公式中,平滑系数 α 的标准取值是?

单选题 · hard

  • A.0.125← 正确答案
  • B.0.25
  • C.0.5
  • D.1.0

答案:A。α=0.125 意味着新 SRTT 保留 87.5% 的旧值、吸收 12.5% 的新样本;β=0.25 用于 RTTVAR 更新。α 取 0.125 是为了让估计值足够平滑,不被单次抖动带偏。

Q10. 快速恢复算法中,为何新 cwnd 要在 ssthresh 基础上加上 3 个 MSS?

单选题 · extreme

  • A.为了加速慢启动的增速
  • B.因为 3 个冗余 ACK 证明已有 3 个报文段成功到达对端并离开网络← 正确答案
  • C.因为 ssthresh 计算时少算了 3 个 MSS
  • D.为了填满接收方的接收窗口

答案:B。3 个冗余 ACK 意味着有 3 个比丢失段序号更大的报文段已到达接收方,这些段占用的网络容量已被释放。所以 cwnd 在减半的 ssthresh 基础上加回 3 个 MSS,相当于“承认这 3 段已走完的路程”,既不低估网络容量也不过度激进。