文章目录
引言
对于网络中的传播效率来说,重传超时中的超时时间一定是十分重要的,时间太短,会向网络中引入大量不必要的重复数据,如果时间太长,倘若超时发生则使得网络的整体利用率下降,而网络环境并不是静态的,一成不变的,所以RTT(round-trip-time)的测试就变得尤为重要和复杂。
RTO(retransmission timeout)计算方式
首先我们介绍一个计算的方法,其实不必太过纠结这个,我们要做的
首先要计算Smooth RTT的值 即SRTT
SRTT <— f ( SRTT ) + ( 1 - f ) RTT
其中f为平滑因子 一般取值为0.8 到 0.9
得到SRTT后我们需要进行下面的计算
rttvar <— ( 1 - h )( rttvar ) + ( h )( | RTT - SRTT |)
RTO = SRTT + 4( rttvar )
h为新平均偏差样本
由上面的计算过程我们可以看到只要我们可以在TCP传输过程中得到相对准确的RTT 那么我们就可以得到理论上准确的RTO
计算RTT中出现的问题
在上面看来计算RTO好像并不是难事,我们只要得到RTT就可以计算,TCP的选项中不是有一个时间戳选项吗(TSPOT),如果在每个ACK的选项中加上时间戳选项不就好了吗,事情当然不会这么简单,在上面我们谈到网络不是一成不变的,可能我们的包会出现丢失,失序,重复,而且并非对每个收到的报文段都返回ACK,这些情况下我们又该这么保证RTT是一个正确的RTT呢
首先Linux采用如下算法来应对各种情况
- TCP发送端在TSPOT中携带一个四个字节的时间戳,该值是发送时的TCP时钟值
- TCP接收端记录接收到的时间戳 称作TSV 并维护一个LastACK的变量 记录下一个期望得到的序列号 这个时候有两种情况 第一种情况就是得到了与LastACK相同的序列号的数据包 那么没有什么问题返回一个ACK 其中携带着TSV的值 这个时候RTT的计算即是数据包一个来回的时间 一切正常。第二种情况就是得到的数据包的序列号不等于LastACK的值 那么这个时候我们可以想到接收方的窗口必定是空缺的 也就是说出现了失序报文段 即出现了丢包或者一个包传输过慢 这个时候的Linux的做法是返回一个ACK 其中TSV的值是接收端收到的最近的有序报文段的TSV 即接收端窗口的最左端 这样做会使的此次RTT的计算并不是实际的RTT 而是大于实际的RTT 则会是RTO的计算变大,这样是有利的 发送端有更多的时间去判断究竟是失序还是丢包,从而减少不必要的数据包的发送,降低重传积极性。
- 接收端收到窗口中缺失的报文段的时候(重传成功或是失序的数据包),这个时候窗口前移,更新RSV为来自最新到达的报文段的RSV,即缺失的报文段的RSV 使得RTT更大 在失序是是有利的 ,过分积极的重传可能导致伪重传
- 当一个ACK对应着多个数据包的时候 回复的RSV是数据包中最早的一个RSV
- 发送端收到ACK后 用当前时间减去其中时间戳的值即得到了RTT 便可开始计算RTO了
经过以上的计算 我们就可以得到一个相对来说动态且准确的RTO 就可以用来触发超时重传了(RTO还可防止零窗口时Window Update报文丢失造成了类死锁现象)
超时重传
当我们经过上述步骤得到了一个准确的RTO后我们就可以进行超时重传了,其实机制理解起来很简单 就是给每一个报文段序列号注册一个计时器 倘若在RTO内收到了ACK 那么取消计时器 反之 则触发超时重传,在触发了重传超时后 TCP就会减小窗口大小,且以成倍 增加退避系数(karn算法), 当收到非重传数据时退避系数降低为1。
超时重传虽然好,但确不是最优选择,因为RTO一般来说都比较大,一般成倍于RTT,所以基于计时器的重传会使得网络利用率的下降,所以引出了另外一种高效的重传策略 快速重传
快速重传
快速重传并非是基于发送端维护的环境变量来决定重传,而是基于接收端反馈的信息来进行重传,由上文中我们可以知道发送端会在接收端丢包的时候收到重复的ACK,因为当接收端所接受到的ACK不匹配LastACK时 会回复接收端窗口的最左端的序列号,所以发送端在接收端丢包的时候会接收到重复ACK,但也有可能是出现了所谓的伪重传现象,这个时候就需要一个阀值(dupthresh或重复ACK阀值),一般情况设置为3,也基于失序程度动态调节,当发送端接收到阀值数量的相同ACK时即触发快速重传,一般情况下快速重传也伴随着滑动窗口的变化。
这其中还有一个恢复点与部分ACK的概念,下面简单谈一谈:
恢复点
发送端在执行重传前已发送的最大序列号称为恢复点
通过这个概念我们可以知道当发送端收到的ACK等于或大于恢复点时我们就可以认为已发送的所有包全部被收到 即重传状态结束 退避因子设置为1。
部分ACK
有了恢复点的概念就出现了这样一种情况,就是在发送端恢复点与当前接收的最大ACK之间可能丢了不止一个包,发送窗口最左端的包重发但接收端还没收到的时候,接收端回复的ACK还是这一个已重发的包的序列号,但当接收端收到重发的ACK的时候 就会使得接收端窗口右移,回复的ACK是下一个丢失的包,在发送端看来,突然收到了一个ACK,这个ACK大于之前收到的AVK,但却小于恢复点,我们把这种ACK称为部分ACK(较旧的版本没有这个概念),当收到部分ACK的时候 立即触发快速重传。
快速重传(SACK)
以上的快速重传相比于超时重传来说效率已经高了不少,但是还是有一个缺陷,就是在每个RTT内最多获取一个空缺,这不就浪费了TCP的选项吗
因为TCP的头部长度有四位 最大能表示的数字为15 每个数字代表4个字节 所以TCP头部最大是60个字节 其中前20个字节是固定的头部结构 意味着我们有40个空闲的选项可使用,所以就有了带选择确认的快速重传,我们用四个字节表示区间的一端,即八个字节可以表示一个空缺,减去一个字节的选项类型和一个字节的选项长度,意味着有SACK的情况下我们能够在一个RTT内最大传输4个空缺,但一般情况SACK会与TSPOT一起使用所以一般最大可传输三个空缺,这就大大提高了重传的效率,尤其实在RTT较大,丢包情况严重的时候。
SACK发送端行为
由接收端产生SACK信息可充分利用SACK,但发送端也应该提供SACK的功能,称作选择性重传(selective retransmission),即在发送端不仅维护ACK信息,也同样维护SACK信息,当收到的ACK大于SACK中的范围时重传成功,利用该信息重传正确的需要重传的数据。
发送端执行重传的时候可能有以下几种情况
- 超时
- 超过阀值的重复ACK
- 部分ACK
- SACK
值得注意的是SACK选项是建议性的 也就是说SACK也可能出错,但TCP保证是可靠的,所以SACK发送端不能根据SACK去清空缓冲区数据和更新计时器,只有在接收到ACK大于序列号的值时才可清除小于ACK序列号的缓存。
伪超时
有一个问题在前面一直有提到但没有细提,就是伪超时,其概念很好理解,就是当超时时间到达时包未丢失,只是传递较慢,而发送端任然进行了重传,这就是所谓的伪重传现象。要解决这个问题我们首先需要判断出现伪重传,然后才能解决伪重传
下面介绍几个检测伪重传的方法:
DSACK
即重复SACK 可在第一个SACK块中告知发送方 重复的SACK块,与通常SCAK不同,DSACK不会在多个ACK中重复,鲁棒性没有一般SACK高。
DSACK缺陷在于检测出伪超时现象需等到伪超时的包到达接收端且返回 时间较长
Eifel检测算法
Eifel检测算法是基于时间戳的,其基本思想就是当伪超时发生后检测收到的ACK 若为第一次发出数据的确认可对比其时间戳 因为时间戳是递增的 所以如果小于重传时的时间戳则发生了伪超时 这种方法相比于DSACK提前了检测超时的时间
下面是检测到后的处理
Eifel响应算法
虽然叫Eifel响应算法 但实际上和Eifel检测算法是分离的,是可以和上面提到的所有检测算法结合的,根据尽早检测到伪超时的检测方法与Eifel响应算法结合称为伪超时, 通过较迟检测到伪超时的检测方法与Eifel响应算法结合称为迟伪超时,两者的行为是不同的,但都会重新设置拥塞控制状态,且在接收到超时重传的ACK后都会更新RTO的值,因为如果伪超时已经发生,我们能做的就是调整RTO,防止下次发生,其中涉及到的拥塞控制在另一篇博客中。
重复
重复出现的不多,但也有可能出现,但比较好处理,通过SACK,可以清楚的看到每个重复的ACK中都没有失序信息,所以一定可以判断是重复信息。
以上就是TCP超时与重传方面的简单介绍 其中涉及流量控制与拥塞控制的方面会在后面几篇博客中介绍。