📚 第六章 TCP与UDP
📄 一句话概括

RTO 不能拍脑袋:太短会误重传,太长会白等,所以要用「平滑 RTT」估。 公式是「SRTT 加四倍偏差」,而且每次只改八分之一,避免被单个异常带走。

📝 本节和前面几节的关系

6.4.2 讲了「怎么发现丢包」,这一节讲「等多久没确认才算丢」。 4.4.1 的往返时间概念在这里第一次被精确化,虽然那节讲的是路由选路。

一、为什么不能拍脑袋

RTO 定得太短,会把「只是慢」误判成「丢了」:于是重传一个其实已经到的包,白白浪费带宽, 而且重传之后又更拥塞,形成恶性循环。 定得太长,丢包要等很久才补:用户看到的是「卡一下」。 所以 RTO 必须「跟着网络状况变」,而「网络状况」唯一的观测手段就是「实测的往返时间」。

二、RFC 6298 的公式

做法分两步:先平滑 RTT,再按偏差放大。

量 怎么更新 作用
SRTT 旧值乘 0.875 加新样本乘 0.125 平滑后的往返时间
RTTVAR 旧值乘 0.75 加偏差乘 0.25 波动有多大
RTO SRTT 加四倍 RTTVAR 实际用的超时

第二行那个「偏差」是「新样本和 SRTT 差多少」的绝对值:它衡量「网络稳不稳」。 稳的网络偏差小,RTO 就接近往返时间; 抖的网络偏差大,RTO 就留出更多余量。 这比「取最近几次的平均」高明得多:平均会把「偶发的一次慢」摊薄,看不出「波动变大」; 而偏差专门盯「波动」,所以能提前把 RTO 抬起来。

三、Karn 算法与退避

有个坑:重传之后测到的 RTT 是不准的——因为你不知道 ack 是回给「原来那个」还是「重传那个」。 Karn 算法的做法很干脆:重传过的段,不做 RTT 采样。 另一个机制是「指数退避」:重传一次不成功,下次的 RTO 翻倍,再不成再翻倍,直到上限。 这样做的好处是「网络真断了的时候不会疯狂重传」, 坏处是「恢复得慢」——所以「断网几秒再恢复」之后,连接往往要过一会儿才顺畅。 RFC 6298 还规定了上下限:下限 1 秒、上限 60 秒。 下限是为了防误重传,上限是为了别死等。 注意这个 1 秒下限在实际高速网络里偏保守,所以有些系统会用更激进的实现, 这也是「不同系统的 TCP 行为不完全一样」的原因之一。

四、动手 Demo:重发超时是怎么算出来的

给五个 RTT 样本,看 SRTT、偏差和 RTO 怎么变。 第四个样本故意给了 300,看它怎么消化。

rtts = [100.0, 110.0, 90.0, 300.0, 105.0]
srtt, rttvar = rtts[0], rtts[0] / 2
for r in rtts[1:]:
    rttvar = 0.75 * rttvar + 0.25 * abs(srtt - r)
    srtt = 0.875 * srtt + 0.125 * r
    print('样本 %5.0f  SRTT %6.1f  偏差 %5.1f  RTO %6.1f'
          % (r, srtt, rttvar, srtt + 4 * rttvar))

实跑输出:

--------------------------------------------------------------------
重发超时是怎么算出来的
--------------------------------------------------------------------
**RTO 不能拍脑袋**:**太短会误重传、太长会白等。**
**所以 TCP 用「平滑 RTT 加四倍偏差」来估。**

  第几个样本     实测 RTT     SRTT      偏差     算出的 RTO
  ----------------------------------------------------------
  1              100          100.0     50.0     300.0
  2              110          101.2     40.0     261.2
  3              90           99.8      32.8     231.1
  4              300          124.9     74.6     423.5
  5              105          122.4     61.0     366.2

**第一行是「初始化」**:**第一个样本直接当基准**,**偏差取它的一半**。
**从第二行起才是平滑**:**每个新样本只改八分之一**,**所以「一个突发的慢包」不会把 RTO 带跑。**

**第四行那个 300 很关键**:**RTO 明显涨了,但没有涨到 300**。
**这就是「平滑」的作用**:**它把「偶发的一次慢」当成了「波动变大」,而不是「常态变慢」。**
**RFC 6298 还规定 RTO 下限 1 秒、上限 60 秒**,**下限是为了防误重传,上限是为了别死等。**
**表里是没做截断的原始值,实际用之前还要按这个规矩夹一下。**

第四个样本 300 进来,RTO 从 231 涨到 423:涨了,但没有涨到 300 以上太多, 这就是「平滑」的作用——它把「偶发的一次慢」当成「波动变大」,而不是「常态变慢」。 第五个样本 105 进来,RTO 又降回 366:因为偏差开始收敛了。 所以这个算法是「自适应」的,网络好它就紧,网络差它就松,不需要人工调参。 表里是没做截断的原始值:实际用之前还要按「下限 1 秒、上限 60 秒」夹一下。 所以真实系统的 RTO 很少小于 1 秒——这也意味着「丢一个包至少要等 1 秒」, 这就是「为什么快速重传那么重要」。

30 秒实验

  1. 算一下:SRTT 是 100、偏差是 10,RTO 是多少?
  2. 想一下:为什么重传过的段不做 RTT 采样?
  3. 想一下:指数退避在网络恢复之后会带来什么影响?

常见误区

  1. 以为「RTO 是个固定值」。 是算出来的,每个连接、每个时刻都可能不同。
  2. 以为「取最近几次平均就行」。 还要看偏差,否则看不出「波动变大」。
  3. 以为「重传之后测的 RTT 也能用」。 不能用,因为分不清 ack 回给谁(Karn 算法)。
自测三问(点开对答案) 1. RTO 的公式是什么? —— **SRTT 加四倍 RTTVAR**,其中 SRTT 是平滑往返时间,RTTVAR 是偏差。 2. 为什么每次只改八分之一? —— **避免被单个异常样本带跑**,让估计值稳定地跟随趋势。 3. Karn 算法解决什么问题? —— **重传之后 RTT 采样不准**的问题:重传过的段直接不采样。

下一节讲连接的建立和关闭。

相关笔记