TCP是可靠传输。可靠之一体现在收到数据后,返回去一个确认。但是不能完全避免的是,数据和确认都可能丢失。解决这个办法就是,提供一个发送的重传定时器:如果定时器溢出时还没收到确认,它就重传这个报文段。
想法是完美的,关键之处在于超时和重传的策略,即怎么决定超时间隔和如何确定重传的频率。
书中举了一个简单的超时重传例子:
如图:
比如A往B传,传了一部分数据后,把B的网线拔了(前边讲过,如果不传数据的话,双方没法知道这个连接已经断了)。然后开始A再给B发数据,此时tcpdump出来发现,连续重传了一个报文段:时间间隔分别是,1.013s, 3, 6, 12, 24 和多个64s...最后发了个复位报文段表示我放弃了。。(从第一次开始发这个报文段,到最后发一个复位段的时间差大约是9分钟,这个9分钟一般在TCP实现中是不变的)。
往返时间测量:
TCP的超时时间很大程度上是依赖报文段的往返时间。因此测量往返时间显得尤为重要。
因为链路上的网络流量或者路由器等的存在,往返时间一般不会是一成不变的,可能会经常发生变化。
最初的TCP规范这样协议:RTT(Round-Trip Time)表示往返时间,用M表示测量到的RTT。
更新过的RTT = 0.9*RTT + 0.1*M 表示,我估计新的往返时间将是,0.9倍的之前的RTT + 0.1倍的新测量的RTT。(0.9叫平滑因子)
得到了新的估计RTT,推荐的重传超时时间RTO(Retransmission TimeOut)的值应该设置为
RTO = RTT*b (这里的b是一个推荐值为2的时延离散因子)。超时时间就是大约2倍的往返时间。
以上这个计算超时时间的方法存在缺陷,[Jacobson 1988] 作出了详细分析,当RTT变化范围比较大的时候,这个方法显得力不从心了,可能会引起不必要的重传。这样当网络负载比较高的时候,再重传会火上浇油...
这就又有了新的方法计算重传超时时间:
前边说到如果RTT变化范围较大时,容易发生不必要重传。学过数学的都会知道,方差可以体现出波动大小。这个方法就是用到了方差来均衡下。
这里有个公式用来计算RTO,懂的原理就好了,这个计算RTO的公式依赖于估计的RTT和均值偏差(逼近与标准差),而最初的方法则使用了被平滑的RTT的一个倍数(b=2)。这块知道是这么个事就好~
往返时间RTT的测量:
如图:
左边的时间轴上有三个括号,它们表明为进行RTT计算对哪些个报文段进行了计时,并不是所有的报文段都被计时。在发送一个报文段时,如果给定连接的重传定时器已经被使用,则该报文段不被计时。如图报文段4或者报文段7都没有参与计时。
对每个连接而言,除了这个滴答计数器,报文段中数据的起始序号也被记录下来。当收到一个包含这个序号的确认后,该定时器就被关闭。如果ACK到达时数据没有被重传,则被平滑的RTT和被平滑的均值偏差将基于这个新测量进行更新。
在每次调用500 ms的TCP的定时器例程时,就增加一个计数器来完成计时。这意味着,如果一个报文段的确认在它发送550 ms后到达,则该报文段的往返时间RTT将是1个滴答(即500 ms)或是2个滴答(即1000 ms)。
如图RTT测量和时钟滴答:
-拥塞举例:
主机slip总是通告窗口大小为4096,而主机vangogh则通告窗口为8192。
如图:
报文段45丢失了,报文段58是正常接收43的报文段给出的确认,然后接着接收主机连续发了8个ack 6657。可以看出是重发第三次(除了正常确认的中第3个)时,发送主机重传发送了63报文段。
这收到第三个ack才重传也是算法中要求的,当收到第3个时,就假定一个报文段已经丢失并重传自那个序号起的一个报文段。这就是Jacobson的快速重传算法。
值得注意的是,在重传后(报文段63),发送方继续正常的数据传输(报文段67、69和71)。TCP不需要等待对方确认重传。
这里再分析一下接收端是怎么处理的: 当按序收到正常数据(报文段43)后,接收TCP将255个字节的数据交给用户进程。但下一个收到的报文段(报文段46)是失序的(数据的开始序号 6913 并不是下一个期望的序号 6657)。TCP保存256字节的数据,并返回一个已成功接收数据的最大序号加1(6657)的ACK。被vangogh接收到的后面7个报文段(48, 50, 52, 54, 55, 57和59)也是失序的,接收方TCP保存这些数据并产生重复ACK(TCP实现没法告诉对方,我就缺某某个报文段,它只能告诉发送方我的确认序号一直是这个)。
当缺少的报文段(报文段 63)到达时,接收方TCP在其接收缓存中组合好第6657~8960字节的数据,并将这2304字节的数据交给用户进程。所有这些数据在报文段72中进行确认。
值得注意的是,此时该ACK通告窗口大小为5888(8192-2304,原来的通告窗口大小是8192),这是因为用户进程此时还没有读取出这些缓存中的字节。
-拥塞避免:
该算法假定由于分组受到损坏引起的丢失是非常少的(远小于1%),因此分组丢失就意味着在源主机和目的主机之间的某处网络上发生了拥塞。
有两种分组丢失的指示:发生超时和接收到重复的确认(如果使用超时作为拥塞指示,则需要使用一个好的RTT算法)。
前边讲过慢启动,拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希望降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法通常在一起实现。
-快速重传和快速恢复算法:
在前边拥塞举例时,观察到第三个ack过来,发送端才进行重传。这是因为:由于我们不知道一个重复的ACK是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们等待少量重复的ACK到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1 ~ 2个重复的ACK。 如果一连串收到3个或3个以上的重复ACK,就非常可能是一个报文段丢失了。于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出。这就是快速重传算法。
接下来收到重传的ACK以前,发送了3个新的数据的报文段(报文段67,69和71)。执行的不是慢启动算法而是拥塞避免算法。这就是快速恢复算法。在这种情况下没有执行慢启动的原因是由于收到重复的ACK不仅仅告诉我们一个分组丢失了,而是在收发两端之间仍然有流动的数据(由于接收方只有在收到另一个报文段时才会产生重复的ACK,而该报文段已经离开了网络并进入了接收方的缓存),因此我们不想执行慢启动来突然减少数据流。
重新分组:
当TCP超时并重传时,它不一定要重传同样的报文段。而是TCP允许进行重新分组而发送一个较大的报文段,这将有助于提高性能(当然,这个较大的报文段不能够超过接收方声明的MSS)。
如图:
第3个发送前,断开网线。开始发送3,此时发生了重传,在放弃连接前,又键入了几个字节。然后插上网线,发现第8行,是把前边两次的分组组装成了一个分组发过去的。
TCP的超时重传...end