前言

网络环境是复杂多变的,而TCP是可靠的,在数据即Segment丢失或延迟的时候,就要发送方重新发送,那么发送方需要等多久发送未收到ACK的包呢?本篇希望可以告诉你如何计算数据重发的超时时间

重传时间

先看两个定义,RTT和RTO

  • RTT:即Round Trip Time(往返时间),表示发送方的Segment从发送到收到对应的ACK的时间差。
  • RTO:即Retransmission Timeout(重传超时时间),表示发送方将Segment发出后,没收到ACK会进行重传的超时时间。

RTO的长短对于重传至关重要:

  • RTO长了:重传的很慢,效率和性能会很低。
  • RTO短了:还没来得及收到ACK,就已经重传了,如果网络有一点拥堵,那么重传就更拥堵了,更拥堵就又重传…。

image-1702220143734

如图,左图RTO较长、右图RTO较短

那么是不是RTO比RTT大一点就好了呢,然而网络环境比较复杂,带宽不同、数据大小不同、拥塞情况不同等等都需要考虑在内,所以RTT也是动态变化的,所以RTO是不能固定一个特定的值的,它是需要动态变化的。

下面来看看TCP都有哪些算法来动态计算RTO。

不同的算法

image-1702221269139

图片来源参考

RTO的计算方式经历过了很多的版本和优化。

经典算法

RFC793记录,算法过程如下:
1、首先通过RTT计算出SRTT(Smoothed Round Trip Time,平滑往返时间)
SRTT = (α * SRTT) + (1 - α) * RTT

SRTT的计算也叫加权移动平均。
计算出的值当RTT增加时,SRTT也会平滑增加;当RTT减少时,SRTT也会平滑减少;
所谓的平滑,可以理解成当RTT变化后保持不变,然后SRTT会一点点移动向RTT的值。

2、基于SRTT,计算RTO
RTO = min[UBOUND, max[LBOUND, (β * SRTT)]]

其中涉及的几个变量:

  • α(ALPHA):作为平滑因子,一般为0.8或0.9
  • UBOUND:RTO的上限值,可以是例如1分钟
  • LBOUND:RTO的下限值,可以是例如1秒钟
  • β(BEAT):作为延迟方差因子,一般在1.3到2.0之间

Karn/Partridge算法

RFC 6298记录。

更详细的文章讨论参考

在经典算法中,有个问题需要考虑到,就是ACK哪个Segment的时间作为RTT样本。即不确定使用第一次发数据的时间和ACK之间时间差还是进行重传的时间与ACK之间时间差。是一种歧义。

image-1702308721705

如下图所示:

  • 左图:没有ACK,进行了重传 ,如果计算第一次发包和ACK的时间,明显算大了。
  • 右图:ACK还没到就进行了重传,如果以重传的包和ACK计算时间,明显算小了。

所以在1987年有了Karn/Partridge算法,这个算法指出了忽略重传计算,即重传的包不进行RTT采样,用单独的重传队列。

但是该方式会有个情况就是:如果某段时间,突然网络不稳定,导致大量的重传,那么此时重传的又不算RTT,会导致网络负载加重,RTO此时无法更新。解决此问题的方式是退避back-off),即重传时先将RTO时间翻倍(UNIX当时修改的是受影响因子,反正就是增加RTO),如果新RTO再次到期,则RTO再进一步增加。待到网络正常后,RTO恢复到正常基于SRTT的值。

back-off和SRTT计算相互独立,毕竟没有ACK就没有新的SRTT(SRTT计算依赖于ACK)。

这里有个疑问点
Q:不知道ACK是原始包还是重传包,为啥不用TCP首部Options的时间戳进行计算?
A:因为Karn/Partridge算法提出时,还没有时间戳选项,时间戳选项被定义是在1992年的RFC1323中。后来又因为受实现影响,Karn/Partridge算法仍是个重要且广泛的实现方案,时间戳计算RTT更多是在现代网络实现中。

Jacobson/Karels算法

前两个算法都是使用的SRTT,如果只用SRTT,如果RTT变化很大,可能无法很快的感知到,由SRTT慢慢平滑掉了。

所以,在1988年,又推出了新的算法(RFC 6298),该算法引入了新的RTT采样和平滑过的SRTT的差值计算因子,记为DevRTT(Deviation RTT)对应RFC上的RTTVAR。

算法如下:

  • 当第一个RTT被采样,仅有一个RTT,计算RTO如下。
    1、计算SRTT: SRTT = RTT;
    2、计算DevRTT: DevRTT = RTT / 2;
    3、计算RTO: RTO = µ * SRTT + ∂ * DevRTT = µ * RTT + ∂ * RTT /2;

  • 当有新的RTT被采样,得到新的RTT‘,计算RTO如下(顺序不能变)。
    1、计算新的DevRTT: DevRTT’ = (1 - β) * DevRTT + β * |SRTT - RTT‘|;
    2、计算新的SRTT: SRTT’ = (1 - α) * SRTT + α * RTT’ = SRTT + α *(RTT’ - SRTT);
    3、计算新的RTO: RTO’ = µ * SRTT’ + ∂ * DevRTT’

其中涉及的几个变量及值(值可以认为是调试出来的最优解吧):

  • µ:1
  • β(beta):0.25
  • ∂(RFC中的K):4
  • α(alpha):0.125

当前算法仍用在TCP协议中。

管理RTO计时器

重传计时器的作用:发送方的TCP栈会为每一个发送的数据段启动一个重传计时器。如果在设定的时间内(RTO)没有收到ACK,那么发送方会认为数据丢失,重新发送该数据包。

下面关于管理计时器的算法,主要翻译至RFC 6298 Managing the RTO Timer推荐算法如下
1、每一次,当一个包含了数据的Segment(包括重传包)被发送,如果重传计时器没有启动,则启动它并在RTO秒后过期。

2、当所有的传输数据都收到了ACK,关闭重传计时器。

3、当收到新消息(之前未被确认ACK的消息)的ACK后,重新启动重传计时器,使其在RTO秒后过期。当然如果没有新数据需要发送,则不会进行重启而是关闭计时器。

当重传计时器过期,则会进行如下处理:
1、重传最早发送的没有被ACK的段数据。

2、设置RTO到2*RTO,即2倍的RTO时间(back-off),最大值不能超过上限60秒

3、开启重传计时器,使其在RTO秒后过期。

4、如果在等待一个SYN段的ACK时,计时器过期了,并且RTO少于3秒,那么RTO必须被重新初始化成3秒进行数据的重传。

总结

TCP的超时重传不可忽略,它是保障TCP可靠传输的基础,那么计算超时时间就非常关键了,它影响着传输的效率和整体的性能。目前RTO的计算算法主要基于RFC 6298所制定的标准进行的实现。