TCP协议之重传时间计算
前言
网络环境是复杂多变的,而TCP是可靠的,在数据即Segment丢失或延迟的时候,就要发送方重新发送,那么发送方需要等多久发送未收到ACK的包呢?本篇希望可以告诉你如何计算数据重发的超时时间。
重传时间
先看两个定义,RTT和RTO
- RTT:即Round Trip Time(往返时间),表示发送方的Segment从发送到收到对应的ACK的时间差。
- RTO:即Retransmission Timeout(重传超时时间),表示发送方将Segment发出后,没收到ACK会进行重传的超时时间。
RTO的长短对于重传至关重要:
- RTO长了:重传的很慢,效率和性能会很低。
- RTO短了:还没来得及收到ACK,就已经重传了,如果网络有一点拥堵,那么重传就更拥堵了,更拥堵就又重传…。
如图,左图RTO较长、右图RTO较短
那么是不是RTO比RTT大一点就好了呢,然而网络环境比较复杂,带宽不同、数据大小不同、拥塞情况不同等等都需要考虑在内,所以RTT也是动态变化的,所以RTO是不能固定一个特定的值的,它是需要动态变化的。
下面来看看TCP都有哪些算法来动态计算RTO。
不同的算法
图片来源参考
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之间时间差。是一种歧义。
如下图所示:
- 左图:没有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所制定的标准进行的实现。