前言
本文只是一个简要的笔记,如果想要深入了解,请查阅RFC 2988。适合对TCP
有一定了解和基础的人查看。
每连接单一计时器
我们都能想到,如果给每一个TCP
分段都分配一个计时器,这是最直接的方法。但是这带来了巨大的内存开销和调度开销。难不成一个TCP
通信的服务器负担全用来给分段计时间,这太离谱了,有没有更好的办法,先人已经为我们想到了一个适用至今的方法:采取每个TCP连接单一计时器的设计。
首先我们要明确,这个超时计时器需要实现什么功能:
- 有报文长时间没有接收到,必须提示超时。
- 这个长时间不能太长也不能太短。
如果这里的超时时间太短了,会导致正常传输的数据包被当成无效数据,网络中充斥大量无效重传,浪费网络资源;如果超时时间太长,就会导致数据传输效率的太低,网络延迟提高。
因此RFC2988制定了下面的原则:
- 每次发送包含数据的数据包(包括重传)时,如果定时器没有运行,则启动定时器。如果定时器在运行,就什么也不做。
- 当所有未完成的数据都被确认后,关闭定时器。
- 当接收到新数据的ACK时候,重新启动定时器。
当定时器超时时,执行下面的操作: - 指数退避,发送方设置 RTO 为之前的二倍,并且关闭定时器。
- 重传 TCP 接收方尚未确认的最早的报文段。
- 启动重传定时器。
为什么要有原则 3 ?
假设下面的情况,如果在定时器快到期的时候发送一部分数据。这样在超时前,只能收到定时器刚开始运行时发送的报文段,后续的正常数据包会被当作超时,因此后续的数据都需要重传,这太考验网络的负载和使用者的耐心了。有了原则3,好处自然不用我多说,保证一部分正常数据的不必要重传。
还有就是如果一个ack到来了,说明后续的ack大概率也会到达,即使出现了丢失,也会在两倍的 RTO 内被重传。为什么是两倍呢?举个简单的例子,A和B是两个同时发送的报文段的ACK(假设,一般会将其合并成一个ACK进行发送),发送方在无限接近超时时候收到了A,此时会将定时器重新启动,如果B数据包在网络中丢失,那么发送方再等待一个 RTO 就会知道 B 丢失。这样算下来不就是两倍的 RTO 内会被重传嘛。
RTO的计算
为了计算RTO,TCP的发送方需要维护两个变量:SRTT(平滑往返时间,英文:Smoothed Round-Trip Time)和RTTAR(往返时间变化,英文:Returns the Round Trip Time Variance)。
SRTT、RTTAVR和RTO的计算规则如下:
- 测量RTT之前,需要将RTO设置为3秒,此时指数退避依旧有效。
该计时器实际上会产生2.5秒到3秒的数值。因为使用粒度为G的心跳计时器的实现不应该将计时器设置低于2.5 + G
秒。 - 当进行第一个 RTT 测量时候,主机必须如下设置:
- SRTT <- R
- RTTVAR <- R/2
- RTO <- SRTT + max(G,K * RTTVAR) 这里的K为4
- 当进行了一个后续的往返时间测量 R’ 时,主机必须设置:
- RTTVAR <- (1 - beta) * RTTVAR + beta * | SRTT - R’|
- SRTT <- (1 - alpha) * SRTT + alpha * R’
这里的alpha为1/8,beta = 1/4。(2988里面说是 JK88 建议的,应该是个1988年的论文吧,没找到)。
这里需要注意的是,计算RTTVAR时用到的SRTT,必须是分配前的数值,也就是说,这两个计算的顺序不能改变。
计算 RTO 时候,如果其小于 1 秒,上取整。RTO可以设置最大值,但是最大值应该大于60秒。
获取 RTT 的样本
TCP必须使用Karn-Partridge 算法来获取准确的消息往返时间估计。由于重传的模糊性,举个简单的例子,如果一个报文段发送了一次重传,发送方接受到ACK,但是此时的ACK到底是重传报文段的确认还是第一次发送的报文段的确认,这就是重传的模糊性,发送方不知道到底是哪个报文段的确认,这样得到的RTT会有很大的偏差,当然这个问题可以由TCP的时间戳选项来解决。该算法会忽略重传的数据段。仅使用明确的确认(即仅传送一次的段的确认)来估计往返时间。Karn 算法的第一部分规定,当存在重传模糊性时,RTT 值将被忽略,而不是集成到 SRTT 中。
但是也有问题,这么简单粗暴的方法,举个例子,如果TCP延迟显著增加后发送数据,TCP计算超时,并且根据之前的RTT来重新传输数据,极端情况下,TCP忽略所有的重传数据包的RTT,RTO永远不会更新。
其第二部分就是考虑到了这种不太理想的网络情况。为每次重传的RTO设置“退避因子”,也就是常说的指数退避。在不需要重传的成功数据传输发生之前,不会重置退避因子。该部分会放置网络的拥塞,使其能从任何拥塞问题中恢复,还保证了RTT信息不会丢失,当数据成功传输而无需重传时,可以将伴随的RTT测量添加到SRTT中。
目前很多TCP实现了时间戳,这样就方便多了,发送端再也不需要保存发送分段的时间了,只需要将其放入协议头的时间戳字段,然后接收端将其回显在ACK即可,然后发送端收到ACK后,取出时间戳,和当前时间做算术差,即可完成一次RTT的测量。
需要注意的是:TCP 实现可以在多次回退计时器后清除 SRTT 和 RTTVAR,因为在这种情况下当前 SRTT 和 RTTVAR 很可能是假的。一旦 SRTT 和 RTTVAR 被清除,它们应该采集的下一个 RTT 样本进行初始化(RTO的第二步)。
…..