万字详文:TCP 拥塞控制详解( 二 )


包守恒原则
TCP 维护一个发送窗口,估计当前网络链路上能容纳的数据包数量,希望在有数据可发的情况下,回来一个确认包就发出一个数据包,总是保持发送窗口那么多包在网络中流动 。
传输的理想情况是要同时达到最大的吞吐量和最小的往返延迟,要达到这个目的,连接必须同时满足两个条件:

  • 以链路瓶颈带宽 BtlBw 发包 (带宽利用率最高)
  • 保证链路中没有缓存队列(延迟最低)
包守恒原则是拥塞控制的基础 。
三、TCP 重传机制本文重点介绍 TCP 拥塞控制相关,传输流程不在该范围之内,有兴趣的同学可以查阅相关文档 。不过 TCP 重传逻辑和拥塞控制中的快速重传有关,所以在真正介绍拥塞控制算法之前,先来了解下 TCP 重传逻辑 。
3.1 超时重传 [RFC2988]RTT(Round Trip Time)由三部分组成:链路的传播时间(propagation delay)、末端系统的处理时间、路由器缓存中的排队和处理时间(queuing delay) 。TCP 发送端得到了基于时间变化的 RTT 测量值,就能据此设置 RTO 。
万字详文:TCP 拥塞控制详解

文章插图
 
当一个重传报文段被再次重传时,则增大 RTO 的退避因子γ 。通常情况下γ值为 1,多次重传γ加倍增长为 2,4,8 等 。通常γ不能超过最大退避因子,Linux 下 RTO 不能超过 TCP_RTO_MAX(默认为 120s) 。一旦收到相应的 ACK,γ重置为 1 。
下面介绍几种常用的 RTT 算法 。
3.1.1 rtt 经典算法 [RFC793]
1)首先,先采样 RTT,记下最近几次的 RTT 值 。2)然后做平滑计算 SRTT( Smoothed RTT) 。公式为:(其中的 α 取值在 0.8 到 0.9 之间,这个算法英文叫 Exponential weighted moving average,中文叫:加权移动平均)
万字详文:TCP 拥塞控制详解

文章插图
 
3)开始计算 RTO 。公式如下:
万字详文:TCP 拥塞控制详解

文章插图
 
其中:
  • UBOUND 是最大的 timeout 时间,上限值;
  • LBOUND 是最小的 timeout 时间,下限值;
  • β 值一般在 1.3 到 2.0 之间 。
该算法的问题在于重传时,是用重传的时间还是第一次发数据的时间和 ACK 回来的时间计算 RTT 样本值,另外,delay ack 的存在也让 rtt 不能精确测量 。
3.1.2 rtt 标准算法(Jacobson / Karels 算法)该算法 [RFC6298] 特点是引入了最新的 RTT 的采样
万字详文:TCP 拥塞控制详解

文章插图
 
和平滑过的
万字详文:TCP 拥塞控制详解

文章插图
 
的差值做参数来计算 。公式如下: 1.计算平滑 RTT
万字详文:TCP 拥塞控制详解

文章插图
 
2.计算平滑 RTT 和真实的差距(加权移动平均)
万字详文:TCP 拥塞控制详解

文章插图
 
3.计算 RTO
万字详文:TCP 拥塞控制详解

文章插图
 
4.考虑到时钟粒度,给 RTO 设置一个下界 。
万字详文:TCP 拥塞控制详解

文章插图
 
这里G为计时器粒度,1000ms 为整个 RTO 的下届值 。因此 RTO 至少为 1s 。在 Linux 下,α = 0.125,β = 0.25,μ = 1,∂ = 4 ——这就是算法中的“调得一手好参数”,nobody knows why, it just works…)(Linux 的源代码在:tcp_rtt_estimator) 。
5.在首个 SYN 交换前,TCP 无法设置 RTO 初始值 。根据 [RFC6298],RTO 初始值为 1s,而初始 SYN 报文段采用的超时间隔为 3s 。当计算出首个 RTT 测量结果,则按如下方法进行初始化:
万字详文:TCP 拥塞控制详解

文章插图
 
3.1.3 Karn 算法
在 RTT 采样测量过程中,如果一个数据包初传后,RTO 超时重传,接着收到这个数据包的 ACK 报文,那么这个 ACK 报文是对应初传 TCP 报文还是对应重传 TCP 报文呢?这个问题就是重传二义性的问题 。当没有使用 TSOPT 选项,单纯的 ACK 报文并不会指示对应初传包还是重传包,因此就会发生这个问题 。TCP Karn 算法是对经典算法在重传二义性问题上的一种改进 。该算法分两部分 。1) 当出现超时重传,接收到重传数据的确认信息时不更新 RTT 。即忽略了重传部分,避免重传二义性问题 。2) 对该数据之后的包采取退避策略,仅当接收到未经重传的数据时,该 SRTT 才用于计算 RTO 。


推荐阅读