TCP重传的原理
在TCP重传的理论中,重传计时器是用于决定是否有必要进行数据包重传的一个主要机制。重传计时器维护着一个叫做重传超时(Retransmission timeout,RTO)的值。在使用TCP进行数据包的传送时,重传计时器就会被启动。当收到数据包的ACK,也就是确认数据包时,计时器就会停止。从发送数据包到接收到确认数据包的时间,被称作往返时间(Round-trip time,RTT)。我们将若干个往返时间求和并计算平均值,就可以得出最终的RTO值。
但是在最终计算出RTO值之前,数据的传输操作将会一直依赖于默认的RTT值。这个设置用于主机之间的初始通信,并基于接收到的数据包的RTT进行调整,从而形成真正的RTO值。
一旦RTO值被确定下来,重传计时器就被应用于每个传输的数据包,从而确定数据包是否丢失,如下图所示:
当数据包发送出去,但是接收方没有发送TCP ACK数据包时,发送方就假设原来发送的数据包并没有发送到目标主机,认为它丢失了,于是就进行重传。重传之后,RTO的值翻倍;如果在到达极限值之前依旧没有收到ACK数据包,那么就会进行第二次重传。如果还是没有收到ACK,那么RTO的值就会再次翻倍。那么每次的重传,都会导致RTO的值翻倍,直至收到ACK数据包,或者发送方达到配置的最大重传次数为止。当然我这里所说的每次进行翻倍,是为了便于大家的理解,实际上在计算RTO的时候,是需要几个公式的,这里不做深入研究。一般来说,Windows操作系统会重传5次,而Linux则为15次。这个次数是可以进行修改的。
下面我们来研究一下Lab10-1.pcap这个实验文件,这个文件中包含了TCP重传的情况。可以看到,第一个数据包是一个正常的TCP PSH/ACK数据包,其中的PSH表示Push操作,也就是指当数据包到达接收端后,立刻传送给应用程序,而不是在缓冲区中排队。那么接下来,在正常的情况下,当接收端接收到第一个数据包之后,往往很快就可以看到用于响应的TCP ACK数据包。但是这里的第二个数据包却是一个TCP Retransmission数据包,表明这是一个重传:
而且接下来的几个数据包也都是重传数据包。这里请大家注意的是,重传数据包和第一个数据包相比,它们的Data区段,也就是所传输数据的主体内容,是完全一致的。如果我们展开查看Packet Details面板中的TCP,还可以发现相应的RTO的值等信息,并且这个RTO与每个数据包的发送时间是相吻合的,呈指数增长。
TCP的快速重传
如果接收方接收到了乱序的数据包,就发送重复的TCP ACK数据包。TCP在其头部使用序号和确认字段,以确保数据被可靠接收并以发送的顺序进行重组。我们上次课说过,Seq序列号可以帮助数据进行有序的传输。
如果接收方遇到不符合顺序的序列号,就知道数据包丢失了。那么为了正确地重组数据,接收方必须要得到丢失的数据包,因此接收方会发送一个包含丢失数据包序列号的ACK数据包,这样发送方就可以知道应该发送哪一个数据包了。以下图为例:
如果发送方收到3个来自接收方的重复ACK时,发送方就会假设这个数据包确实在传输的过程中丢失了,于是就立刻发起快速重传的机制。一旦触发了快速重传,其它所有正在传输的数据包都要先暂停发送,直到把丢失的数据包发送出去为止。为什么要规定凑满三个重复ACK数据包呢?我的理解是,网络数据包有些时候在传输的过程中会出现乱序的情况,乱序的数据包一样会触发重复ACK数据包,但是由于出现乱序而重传没有必要。由于一般的乱序的距离不会相差太大,比如3号数据包也许会到5号数据包的后面,但是不太可能跑到7号数据包的后面,所以限定成3个或者以上,就可以在很大的程度上避免由于乱序而导致的快速重传。
这里我们分析一下实验文件Lab10-2.pcap:
我们首先看一下第一个数据包,这个TCP ACK数据包是从数据的接收方发往数据的发送方的,是对前一个数据包的确认。从Ack的值可以知道,接收方目前已经收到了1之前的所有数据,而这个值也应当是接收的下一个数据包的Seq号。然后看一下第二个数据包:
可以发现,这个数据包的Seq的值是10945,并不是我们希望看到的1,这就说明了数据在传输的过程中出现了丢包的情况。当接收方发现这个问题之后,就会向发送方发送一个重复的ACK数据包:
接下来的第4个数据包依旧是发送方所发送的数据,并且依旧是错误的Seq号,于是接收方发送第二个重复ACK数据包,接下来发送方又发送了一个错误的数据包,于是接收方再回复一个重复ACK数据包。
由于发送方收到了三个重复的ACK数据包,那么就会停止所有数据的发送工作,并重新发送丢失的数据包,也就是第8个数据包的内容:
可以看到,这里已经标出了这个数据包就是一个快速重传数据包,其大小为1368。那么下一个数据包就是对这个快速重传数据包的回应,从Ack=1369可以知道,已经接收到了1369之前的所有数据。
多个数据包丢失的情况
最后我们再讨论一个比较复杂的情况,也就是丢失了多个数据包的情况。假设发送方一共发送了8个数据包,但是其中的2号和3号数据包丢失了,而1、4、5、6、7、8号数据包都抵达了接收方,并且触发了Ack 2,也就是通知发送方,没收到2号数据包。对于发送方来说,只能通过Ack 2知道2号数据包丢失了,但是并不知道还有哪些数据包丢失了。那么在重传了2号数据包之后,接下来应该传哪一个呢?有以下三种方案: 方案1:把3、4、5、6、7、8号数据包重传一次。这是最简单直接的办法,但是这个丢包的后果导致了多个数据包被重传,效率是非常低的。其实早期的TCP协议就是这样处理的。 方案2:接收方收到重传过来的2号包之后,会回复一个Ack 3,这样发送方就可以知道3号数据包也丢失了,于是传送3号包。这样当接收方收到重新传送过来的3号包之后,由于所有的数据包都收到了,就回复一个Ack 9,那么发送方就可以从9号数据包开始发送数据了。这个方案被称为NewReno,在RFC2582和RFC3782中被定义。NewReno的方法虽然说比较完善,但是在丢包量很大的时候,就需要花费多个往返时间来重传所有丢失的数据包。 方案3:接收方在请求重传2号包的时候,顺便把收到的数据包号告诉发送方。所以网络上数据的传输过程因该是这样的: 在收到4号数据包时,告诉发送方已经收到4号,但是2号没收到。 在收到5号数据包时,告诉发送方已经收到4、5号,但是2号没收到…… 这样一来,发送方就可以对丢包的细节了如指掌,在快速重传了2号数据包之后,它可以接着传3号,然后再传9号数据包。这种方案被称为SACK,在RFC2018中被定义。
这里我们依旧看一下实验文件Lab10-2.pcap,查看一下它的第3个重复的ACK数据包,在Packet Detail面板中,展开TCP中的Options,查看一下SACK的内容:
可以看到,SACK后面的值为5473-15049,表示序列号为5473到15049的数据已经接收到了,结合Ack=1,那么发送方就知道序列号为1到5472的数据没收到,于是接下来就发送这些数据。