目录:
RTT 和 RTO
拥塞避免算法
快速重传与快速恢复算法
TCP通过在发送时设置一个定时器来解决数据和确认都有可能会丢失的问题。如果当定时器溢出时还没有收到确认,它就重传该数据。对任何实现而言,关键之处就在于超时和重传的策略,即怎样决定超时间隔和如何确定重传的频率。
1.往返时间 RTT 和重传数据 RTO 测量
由于路由器和网络流量均会变化,因此我们认为这个时间可能经常会发生变化,TCP应该跟踪这些变化并相应地改变其超时时间。
首先TCP必须测量在发送一个带有特别序号的字节和接收到包含该字节的确认之间的RTT。用M表示所测量到的RTT。
Jacobson算法:
Err = M-A
A ← A+g*Err
D ← D+h( |Err|-D)
RTO = A + 4D
A是被平滑的RTT(均值的估计器),而D则是被平滑的均值偏差。Err是刚得到的测量结果与当前的RTT估计器之差。A和D均被用于计算下一个重传时间(RTO)。增量g起平均作用取为0.125。偏差的增益是h,取值为0.25(计算均可只通过移位操作完成)。当RTT变化时,较大的偏差增益将使RTO快速上升。
Karn算法
重传多义性问题:假定一个分组被发送。当超时发生时,RTO正进行退避,分组以更长的RTO进行重传,然后收到一个确认。那么这个ACK是针对第一个分组的还是针对第二个分组呢?
当一个超时和重传发生时,在重传数据的确认最后到达之前,不能更新RTT估计器,因为我们并不知道ACK对应哪次传输(也许第一次传输被延迟而并没有被丢弃,也有可能第一次传输的ACK被延迟)。并且,由于数据被重传,RTO已经得到了一个指数退避,我们在下一次传输时使用这个退避后的RTO。对一个没有被重传的报文段而言,除非收到了一个确认,否则不要计算新的RTO。
大多数源于伯克利的TCP实现在任何时候对每个连接仅测量一次RTT值。在发送一个报文段时,如果给定连接的定时器已经被使用,则该报文段不被计时 。
在上面发送的6个报文段中,只有3个报文段被用来计算RTT:报文段1、报文段3和报文段7。
发送报文段1时,定时器启动,并在确认(报文段2)到达时终止。下一个被计时的是报文段3。传输报文段4时,由于连接的定时器已经被启动,因此该报文段不能被计时。在报文段5(3的确认)到达时计算出第二个RTT。
由于TCP使用 500ms 定时器,因此TCP计算出的RTT都是500ms的整数倍,可能与RTT的实际时间存在差异。如果一个报文段的确认在它发送550 ms后到达,则该报文段的往返时间RTT将是1个滴答(即500
ms)或是2个滴答(即1000 ms) 。
2.拥塞避免算法
拥塞避免算法是一种处理丢失分组的方法,有两种分组丢失的指示:发生超时和接收到重复的确认 。
拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希望降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。
拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh。这样得到的算法的工作过程如下:
1) 对一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节。
2) TCP发送窗口的大小不能超过cwnd和接收方通告窗口的大小。
3) 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)。如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动)。
4) 当新的数据被对方确认时,就增加cwnd。如果cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进行拥塞避免。
拥塞避免算法要求每次收到一个确认时将 cwnd增加1/cwnd。与慢启动的指数增加比起来,这是一种加性增长。
3.快速重传与快速恢复
在收到一个失序的报文段时,TCP立即需要产生一个ACK(一个重复的ACK)。这个重复的ACK不应该被迟延。该重复的ACK的目的在于让对方知道收到一个失序的报文段,并告诉对方自己希望收到的序号。
重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1~2个重复的ACK。因此如果一连串收到3个或3个以上的重复ACK,就非常可能是一个报文段丢失了。于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出,这就是快速重传算法。接下来执行拥塞避免,而不是慢启动,这就是快速恢复算法。
没有执行慢启动的原因是:由于收到重复的ACK不仅告诉我们一个分组丢失了。由于接收方只有在收到另一个报文段时才会产生重复的ACK,而该报文段已经离开了网络并进入了接收方的缓存。也就是说,在收发两端之间仍然有流动的数据。
这个算法通常按如下过程进行实现:
1) 在收到第一个重复的ACK到收到第3个重复的ACK之前,cwnd保持不变。
2)当收到第3个重复的ACK时,将ssthresh设置为当前拥塞窗口cwnd 的一半。重传丢失的报文段。设置cwnd 为ssthresh加上3倍的报文段大小(因为收到3个重复的ACK)。
3)每次收到另一个重复的ACK时,cwnd增加1个报文段大小并发送1个分组(如果新的cwnd允许发送)。
4)当下一个确认新数据的ACK到达时,设置cwnd 为ssthresh(在第2步中设置的值)。这个ACK应该是在进行重传后的一个往返时间内对步骤1中重传的确认。另外,这个 ACK也应该是对丢失的分组和收到的第1个重复的ACK之间的所有中间报文段的确认。
cwnd、ssthresh计算
假设一个报文段大小为512字节,cwnd 初始化为1个报文段,ssthresh设置为12个报文段。在cwnd增加到16报文段大小时检测到阻塞(超时或重传)。
超时发生时,cwnd 和 ssthresh 变化:
重传发生时,cwnd 和 ssthresh 变化:
1)在 cwnd 小于 ssthresh 时,使用慢启动算法;当 cwnd 大于等于 ssthresh 时(时刻3,因为8+8大于12,所以cwnd增加1),使用拥塞避免算法,cwnd 线性增长。
2)当 cwnd 到达 16 时(时刻11),收到第一个重复的 ACK,在收到第三个重复的 ACK 之前,cwnd 保持不变;
3)当收到第三个重复的ACK(时刻14),立刻重传丢失的数据报,ssthresh 设为当前 cwnd 的一半(16/2),cwnd 设为 ssthresh 加上 3 倍数据报大小(8+3);
4)在收到重传数据报ACK之前,每收到一个重复的ACK,cwnd 加 1 (上图中,重传后还收到了两个重复的ACK);
5)当收到重传数据报的ACK时,cwnd 设为 ssthresh。