为了能够顺利的进行TCP的操作,大多数的TCP实现至少要使用4个计时器,见下图。
重传计数器
为了重传丢失的报文段,TCP应用了一个重传计时器(在整个连接期间)来处理重传超时(RTO),也就是对报文段的确认的等待时间。我们可以以为重传计时器定义一下规则:
- 当TCP发送了位于发送队列最前端的报文段后,就启动这个计时器。
- 当这个计时器超时后,TCP重传位于发送队列最前端的报文段,并重启这个计时器。
- 当一个(或多个)报文段被累计确认后,这个(或这些)报文段被消除出队列。
- 如果队列为空,TCP停止这个计时器,否则TCP重启计时器
往返时间(RTT)
要计算重传的超时期限(RTO),我们首先要计算往返时间(round trip time,RTT),但是计算TCP中的RTT是个复杂的过程,我们将通过一些例子逐步了解它。
测量RTT
我们需要找出从发送出去一个报文段到对它的确认需要多少时间。这就是测量RTT。我们应当记住,报文段和他的确认之间并非一对一的关系,好几个报文段有可能会被一起确认。一个报文段的测量RTT是指这个报文段到达终点并被确认需要的时间,虽然这个确认可能还包括对其他报文段的确认。请注意,在TCP中,任何时刻只能有一个正在运行的RTT测量。也就是说,如果RTT测量开始了,那么在这次的RTT测量结束之前,不能再开始其他测量。我们使用记法RTT(m)表示测量RTT。
平滑RTT
测量RTT很可能每次往返都有变化。在目前的因特网中,RTT测量值的起伏非常大,以至于单词测量值无法被用于重传超时的目的。绝大多数实现使用的是一种平滑RTT,记为RTT(s),它是对RTT(m)和前一个RTT(s)的加权平均,如下表示:
α的取值与实现有关,但通常为1/8.换言之,新的RTT(s)是由7/8的旧RTT(s)和1/8的当前RTT(m)相加而成。
RTT的偏差
大多数的实现不仅使用了RTT(s),还要计算RTT的偏差,称为RTT(d).它是根据RTT(s)和RTT(m)并使用下面公式计算得到的:
β的取值与实现有关,但通常为1/4.
重传超时(RTO)
RTO的数值基于平滑的往返时间和它的偏差值。绝大多数实现使用下面的公式计算RTO:
换言之 ,就是用但钱的RTT(s)值,加上当前的RTT(d)值(通常是个较小的数)的四倍。
下图所示为一个连接的一部分。图中画出了连接建立阶段和部分数据传送阶段。
1、当SYN报文段发送后,没有RTT(m)、RTT(s)或RTT(d)的值。RTO值设为6.0秒,下面给出此时这些变量的值为:
2、当SYN+ACK报文段到达时,测量出RTT(m)等于1.5秒,下面给出的这些变量的值:
RTT(m)=1.5
RTT(s)=1.5
RTT(d)=1.5/2-0.75
RTO=1.5+4*0.75=4.5
3、当第一个报文段被发送后,新的RTT测量就开始了,请注意,在发送发发送ACk报文段时不能开启下一次RTT的测量过程,因为ACK报文段不消耗序号,也没有超时。在发送第二个数据报文段时也没有测量RTT,因为已经有一个RTT测量正在进行之中。最后一个ACK报文段的到达用来计算RTT(m)的下一个数值。虽然最后一个ACK报文段确认了两个数据报文段(累积的),但它的到达完成的是对第一个数据报文段的RTT(m)的计算。现在这些变量的值如下:
RTT(m)=2.5
RTT(s) =7/8 X 1.5 + 1/8 X 2.5=1.625
RTT(d) = 3/4 X(0.75) x | 1.625-2.5| = 0.78
RTO=1.625+4X0.78=4.74
Karn算法
假定有一个报文段在重传计时器超时前未被确认,因而重传。当发送TCP收到对这个报文段的确认时,它无法知道这个确认是对原来报文段的确认,还是对重传报文段的确认,因为新的RTT值要根据报文段发送的时间来计算。但是如果原始的报文段丢失了而确认是对重传的报文段确认,则当前的RTT的计算就必须从重传的报文段发送的时间算起。这种模糊性被Karn解决了。Karn的算法很简单。在计算新的RTT时,不需要考虑重传报文段的往返时间。除非你发送了一个报文段并且在没有被重传的条件下收到了确认,否则不要更新RTT的值。TCP在计算新的RTO时不考虑重传报文段的RTT。
指数退避
如果发生了重传,那么RTO的指数是什么?大多数的TCP使用指数退避的策略。每一次重传,RTO的数值就加倍。因此,如果报文段重传一次,这个值就是两倍的RTO。如果它被重传了两次,这个值就编程了四倍的RTO一次类推。
下图所示为前面例子的延续,这次出现了重传,因而用到了Karn算法。
图中的第一个报文段发送出去,但丢失了。经过4.74秒后,RTO计时器到期。这个报文段被重传,而计时器被设置为9.48秒,原来的RTO值的两倍。这次在计时器超时之前收到了ACK。我们继续等待,直至我们发送一个新的报文段,并收到了对它的确认,然后才能重新计算RTO(Karn算法)。
持续计时器
为了处理零窗口值的通道,TCP需要另一个计时器。如果接收TCP宣布窗口为零,那么发送TCP就会停止发送报文段,直至收到TCP发送来一个宣布窗口大小非零的确认。但是这个ACK报文段可能丢失。应当记住,在TCP中的确认报文段既不需要确认,也不需要重传。如果确认报文段丢失了,接收TCP仍然认为这个确认已经完成了任务,并等待着发送TCP下面的报文段。对于仅包含有一个确认的报文段是没有重传计时器的。发送TCP由于没有收到确认,就等待对方发送一个确认来通知窗口的大小。这两个TCP都在永远等待着对方(死锁)。
为了纠正这个死锁问题,TCP为每个连接使用一个持续计时器(persistence timer)。当发送TCP接收窗口值为零的确认时,就启动一个持续计时器。当持续计时器超时后,发送一个TCP特殊的报文段,称为探测报文段。这个报文段只有1个字节的新数据。它有序号,但它的需要永远不需要确认,甚至在计算剩余数据的序号时,这个序号也被忽略。探测报文段促使接收TCP重传一个确认。
持续计时器的时间长度被设置为重传时间的值。但是若没有收到从接收方发送来的响应,则需要发送另一个探测报文段,并将其持续计时器的值加倍,且该计时器复位。发送方继续发送探测报文段,不断地将持续计时器的值加倍和复位,直到这个值达到一个门限(通常是60秒)为止。在这以后,发送端每个60秒就发送一个探测报文段,直到窗口重新打开。
保活计时器
某些实现中要使用报货计时器(keepalive timer)来防止两个TCP之间的连接长时间空闲。假定一个客户端打开了到服务器的一条连接,传送过一些数据,然后就变的静默了。也许是因为这个客户处了什么故障。在这种情况下,这个连接将永远处于打开状态。
为了解决这个问题,绝大多数的实现都是给服务器设置了一个保活计时器。每当服务器收到客户的信息,就把该计时器复位。超时通常设置为2个小时。若服务器过了两个小时还没有收到客户端的任何消息,它就发送一个探测报文段。若联系发送了10个探测报文段(每隔75秒一个)没有收到响应,它就假定客户出了故障,并终止这个连接。
TIME-WAIT计时器
TIME-WAIT(2msl)计时器是在连接终止期间使用的。参考TCP状态转换图