这一部分将介绍TCP的可靠传输。前面介绍了可靠传输的基本原理,若要实现最基本的可靠传输,必须完成的工作包括要对数据进行编号;接收方要进行接收确认;发送方要设置好超时重传的等待时间。下面将逐一介绍TCP是如何解决这些问题的。另外需要说明的是,TCP的连接提供的是全双工通信,但为了方便,本次讨论只涉及一个方向的数据传输。
1.正常通信时的状况
我们已经知道,TCP是面向字节流的协议,它的数据是按照字节进行编号的。在建立TCP连接时,为字节流随机生成一个初始序号,这个初始序号在0~232-1之间产生,可能会很大。后面讨论问题时,为了方便起见,会用比较小的初始编号做例子,但这并不代表初始编号一定会从类似1这样很小的数字开始。
虽然TCP为每个字节设置编号,但每次数据传输必然不会只传送一个字节。数据段会依据情况,封装不多于最大报文段长度(MSS)的字节进行发送。在通信稳定进行时,数据段内的数据量一般都会和MSS值一致。为了说明起来简单明了,后面的例子中会用整十整百作为每次数据段携带的数据字节数,如某次发送了编号为301~400的100 Byte,但这也并不代表数据段携带的字节数都会如此整装。
每次发送的数据段,依据首部的“序号”字段便可以得知该数据段是从哪个字节开始的。至于数据段内包含有多少字节,这在TCP首部里面是没有字段予以记录的。从密切配合的TCP/IP协议栈来说,每次传输的数据量是由IP记录和处理的。TCP无论是把数据交付给IP,还是从IP获取数据,字节的数量对于TCP的处理程序来说都是明确的,并不需要TCP报文段的首部里面对数据量进行专门记录。由于UDP的首部中有UDP数据报的大小,这个字段可以被视为冗余信息。UDP的首部设置这样一个冗余字段的原因被认为是:UDP需要这样一个2 Byte的占位字段,保证首部长度是8 Byte这样比较齐整的数量。
TCP采用的是积累确认,而不是逐一确认。这样的设置也很好理解,毕竟协议是为每个字节设置数据编号,如果是逐一确认,就要对每个接收到的字节进行确认,开销太大。TCP通过数据段首部的确认号进行确认。需要注意的是:确认号是刚刚接收到的全部数据的最后一个字节的编号加1。可以这样认为,接收方通过确认号表示“在这个号码之前的字节我都正确收到了”。换句话说就是:“我(在我发送这个确认的时候)已经准备要从这个号码开始接收数据了”。虽然TCP采用了针对编号字节的积累确认,但接收方通常仍然需要对收到的每个数据段进行逐一确认,即在正常通信状态下,每次要对某个数据段承载的全部字节进行积累确认。
TCP的可靠传输采用的是滑动窗口方式。在发送方,会为数据设置一个发送窗口,对发送窗口内的数据将采用流水线的方式进行发送。在TCP实际工作中,这个窗口可以很大。若只使用首部中的窗口字段,窗口大小可以达到64 KB。若是使用首部可选项中的窗口扩大选项,窗口容量更是可以达到吉字节的规模。窗口中也可以容纳足够发送上百乃至几十万个数据段的数据,但是在例子中,为了简单明了,窗口也都会给得很小。
按照滑动窗口的规定,只有处在发送窗口内的数据才可以被发送。在得到对方的接收确认包后,窗口会适时向前滑动,让后面的数据进入发送窗口内。另外,大家还需要注意的是,在实际工作中,发送窗口的大小是会依据通信状况而发生变化的。但为了简化讨论流程,暂时假定窗口的大小是不变的。关于发送窗口的变化,会在后面流量控制和拥塞控制两部分章节里面详细介绍。
下面用一个虚拟的例子帮助大家理解TCP的可靠传输。发送方准备通过TCP发送一批数据,建立TCP连接后,其初始字节编码为1。发送窗口大小为400 Byte,最大报文段长度(MSS)大小则为100 Byte。在通信正式开始的时刻,发送方的数据与窗口如图5-9所示,位于发送窗口内的400 Byte会分为4个数据段,以流水的方式发送出去。在不出现任何纰漏的情况下,这4个数据段和其预期收到的接收确认包概况见表5-3的前4行。每次得到对方的接收确认后,窗口都将向前滑动。
图5-9 发送方初始状况
表5-3 无差错情况下,数据段及预期接收确认列表
图5-10示意了发送进行了一段时间后的情况。此时,对数据可以分为发送窗口之外和发送窗口之内进行讨论。在发送窗口之外的数据有两种情况:位于发送窗口左侧的数据都已经确认被对方正确接收了;位于发送窗口右侧的数据,不可以被发送,也尚未被发送。处在发送窗口内的数据也存在两种情况:已经被发送,但尚未收到确认;尚未发送,但可以被发送。如果没有丢包、出错这样的网络问题,已经发送但尚未接收到确认的数据可能有这样3种情况:自己本身在网络传输途中、正在被接收方处理、对方的接收确认已经发出但尚未到达。一般来说,由于网络传输的所需时间要远大于接收方的处理时间,所以,发送窗口中已经发送但尚未确认的数据都是处于自身或者对应的接受确认尚在网络上传输的状态,如图5-11所示。
图5-10 发送进行了一段时间后的情况
图5-11 已发送未接收数据
每当发送方收到对方的接收确认时,被确认的数据将离开发送窗口,成为窗口左侧已经确认接收的数据。这种情况可以视为窗口的左端向前推进了。若是窗口的右端前进时,就意味着新的数据被纳入发送窗口,它们可以被发送了。在这一节中,假定窗口的大小是不变的,这意味着窗口的左、右两侧是同步运动的。发送窗口两侧不同步的运动意味着要改变窗口的大小——这种情况将在后文介绍流量控制和拥塞控制时再进行介绍。
2.出错的情况和应对
当网络工作平稳,没有丢包和校验错误,没有乱序到达时,通信就会平稳地进行下去。发送方不断地把发送窗口内的数据发送出去,当得到接收确认后,发送窗口向前滑动。如此不断进行,直到将需要发送的数据全部发送完毕,通信结束。
但是,网络传输总是有出现错误的可能性的,即使概率再低,也是无法彻底避免的。下面开始探究在错误发生时,为了实现可靠传输,TCP是如何进行处理的。首先介绍一下TCP的接收确认。
前文在介绍可靠传输基本原理时说过,对于发送方,每次发送完一份数据后要保留该数据的一个副本。其实,TCP的发送窗口中保留的“已发送未确认”的数据就是这样的副本。发送方对发送窗口中的数据,无论是否已经发送,都会予以妥善存储。
在接收方,每接收到一个数据段之后,首先要对该数据段进行校验处理。若校验无误,则向发送方回应一个接收确认。需要额外说明的是,TCP在数据传输过程中都会采用“捎带确认”的方式来进行。所谓“捎带确认”,就如同人类一般不会写一份只表达自己“收到”的信函。就是说一般没有专门只起到确认作用的数据段,接收确认都是在给对方发送数据的同时“捎带着”进行的。这就如同人类在信函开头写上“您×月×日的上一封来信我已经收到”表示接收确认;后面再写的诸如“我这边的情况是……”或者“我对××问题的看法是……”就是在发送自己的数据。在具体操作中,就是每个数据段一般都会包含数据,数据段首部的“序号”“确认序号”“ACK位”,都有效或包含有意义的值。ACK位表示“确认序号”有意义;“确认序号”用以做积累确认,序号标识本数据段携带的数据起始位置。这一节虽然为了叙述简便起见,把注意力主要放在单一方向的数据传递,但这里还是要简单介绍一下TCP连接的全双工特性。
当校验出错时,接收方不用做任何其他处理,直接丢弃该数据段。无论是数据段丢失还是校验出错,乃至接收确认出错或者丢失,对于发送方来说,就都需要在等待超时之后重传的操作了。不过我们会看到,由于采用了表示“××编号字节之前都收到了”的积累确认,对于接收确认丢失的数据段,只要后面数据段还有确认包传回发送方,发送方是不需要进行超时重传的。
在发送方超时重传之前,次序在被重传数据段后面的数据段应该有被发送完毕的了。这样的话,在重传的数据被接收到之前,就可能有这样的事情发生:接收方会正确接收到出现错误数据段后面的数据。从接收效果上看来,在接收方的接收数据序列中会因为不连续而出现空洞;但这里关注另一个问题,即接收方在接收到出错数据段后面的数据时,应该如何给出接收确认。
现在TCP一般采取的处理方式是,对于任何正确接收到的数据段,都要发送接收确认。如果因为有数据没能正确接收而出现接收空洞,确认包的确认号,只截至目前最后一个没有空洞位置的数据编号,即积累确认只能确认连续正确接收的数据。在任何一个接收空洞后面,无论有多少正确接收的数据,都无法进行接收确认。
表5-4示意了一段发生数据重传的通信。为了说明方便,假设一个虚假的超时时间,大致相当于发送三个数据段的时长。另外也要注意,网络传输也具有一定时延,发送出去的数据段不会立即收到对方的确认。
从表5-4中可以看出,开始时,传输平稳进行,第3个数据段没有被正确接收,但接下来的数据段都正常接收了。由于第3个数据段没有正确接收,在接收方的接收序列中就出现了空洞。虽然第4、第5、第6数据段都正确接收了,但因为数据出现空洞的原因,它们的接收确认编号只能倒用以确认第2个数据段结束处的201,即表示连续接收数据末尾的200加上1。发送方由于迟迟接收不到第3个及后面数据段的接收确认,就会因为等待超时而开始重新发送第2个数据段后面的数据。当第3个数据段被正确接收后,接收方收到的连续无空洞的数据就到达5个数据段结束处,确认号就可以设置为601来确认到这个位置。后续重新发送的第4个数据段已经被接收过了,在信宿端,重复收到的数据段不再作接收处理,但信宿端仍然需要对这些重复的数据段进行确认数据段的发送。这些确认数据段的确认号将被设置为当前连续接收成功位置后面的字节编号:601。
表5-4 数据丢失重传的情况
如果接收方正确接收了数据段,而接收确认却未能被发送方正确接收,发送方未必需要履行超时重传。表5-5便示意了这种情况。对比表5-4列出的情况,出现问题的依然是第3个数据段,这次是接收确认没能传回发送方,不过,在正确接收到第4个数据段后,接收方拥有的无空洞的连续接收数据编号到达了400,其发回的确认序号会是401。这相当于向发送方表示“编号为401之前的数据我全部正确接收了,我认为你该从编号401开始发给我数据”。这样可以看出,只要后面还有正确接收的数据段,数据没有在这个位置形成接收空洞,偶尔的接收确认丢失是不会引发TCP超时重传的。当然,由于TCP采用了“捎带确认”,接收确认数据段往往携带着对侧传来的数据,必然会触发对侧的超时重传,但对本侧的数据发送一般不会产生影响。
表5-5 确认丢失的情况(www.xing528.com)
3.超时重传时间的设置
前面多次提及,TCP发送方在等待超过一个特定时间没有收到确认,就要重传已经发送过的数据。在前面的例子里,为了说明问题的方便,也主观地规定过一个超时时间。
毫无疑问,这个超时时间的恰当设置非常重要,它对于通信效率有很重要的影响。如果其设置得太长,在真正发生需要重传的情况时,发送方就要做额外的不必要的等候,从而影响通信效率。但如果这个时间设置得太短,可能会使得很多“再稍等一小下下确认包就到达了”的情况变成了超时。这样就会导致发送方增加了很多不必要的重传,同样也会影响通信效率。
但是,在实际的传输过程中,TCP如何确定这个超时时间,是一个非常复杂的问题。这是因为,连接通信参与双方的网络环境千差万别。通信的双方,可能同处一个局域网内,彼此之间有很短的通信时延,网络状况也非常稳定。它们也可能彼此远隔重洋,甚至在通过同步轨道通信卫星这样时延较长的链路进行通信。而且,当双方距离遥远,需要经过多个网络设备时,由于一路上各个设备的各种状况导致排队时延、处理时延变化造成的整体时延变化也会变得复杂。不仅不同网络适宜的超时时间不同,即使同一网络,这个时间也会发生变化。
如何设置这个时间?总而言之,超时时间的设置,要在能保证绝大多数正常数据往返不超时的基础上尽量短。一种简单的想法是,让超时时间比通信双方的平均往返时间长一些,但是,如何确定“平均往返时间”?“长一些”是长多少?这些都是需要妥善解答的问题。
在解答这两个问题之前,先简单探究一下网络时延的概率分布特点。通信往返时间的概率密度可以参考图5-12。把概率密度函数曲线简化成一种接近正态分布的悬钟线。不同的网络条件会有不同的时延,也就有不同的平均往返时间。当网络环境比较简单,比如说局域网中,往返时间变化不大,在概率密度图上显示曲线钟形尖锐高耸,反之则低矮宽阔。对待这些不同的网络,时间设置当然要因地制宜。为了在能保证让绝大多数正常数据往返不超时的基础上让超时时间尽量短,人们利用了类似正态分布“三西格玛原则”的方式来规定超时时间,即将超时时间设置为用一个平均往返时间的近似,加上一个近似方差数的倍数。
图5-12 通信往返时间的概率密度示意
式(5-1)给出了TCP确定超时时间的公式。式中,RTO为超时重传时间;RTTs是报文段平均往返时间的近似取值;RTTd可以视为是样本方差的某种近似取值。它们并不是这两个数值本身,只是某种近似值。探讨它们和真正的平均值、方差的关系远超本书的范围,大家只需要记住计算下面几个公式就可以了。
作为平均往返时间近似的RTTs,其依据一系列采样计算而来。TCP的处理程序不一定为每次的数据段和它的确认都计算往返平均值,一般是定期采集部分的往返时间RTT作为样本。新的RTT样本将按照式(5-2)与旧的RTTs迭代出新的RTTs数值。
从式(5-2)可以看出,RTTs根本不是往返时间的平均,它只是旧的数值和新采样之间的一个加权平均。但是这个公式的好处就是非常易于计算,比起记录至少是一定时段内的多个采样值并每次真正计算平均方便得太多。作为加权值的α,其取值范围为[0,1]。α越接近0,新的RTTs就越少受新采样RTT的影响,就越不易变动。α越接近1,新的RTTs就越容易迎合新采样带来的变化。在TCP的处理中,典型的α取值为0.125,即1/8。RTTs的初始值就设置为第一次采样的RTT值。
式(5-3)给出了RTTd的计算方法。RTTd的初始取值为第一次RTT采样值的一半。被称作“偏差值”的RTTd的计算,即使用旧的“偏差”和新样本与平均值之间的“新偏差”做加权平均计算。作为加权值的β也取自[0,1],它的推荐数值是0.25,即1/4。
关于“采样”的说明:首先,用于采样的数据段和确认段需要在它们的首部里面加入选项中的时间戳信息。其次,不是每次采样都会被RTO更新机制正常接受的,更明确地说,若采样时发生了重传这样的事件,这次采样可以按作废处理,而不用于更新RTTs和RTTd的值。
但是,网络传输时延因为突发故障而突然增大的可能性也是存在的。一种比较极端的例子就是中美海底光缆曾经发生过断裂,导致跨洋带宽和时延都大幅恶化。如果不能及时扩大超时等待时间,以让TCP连接迅速适应类似的急剧变化,对于通信是十分不利的。所以,对于发生了超时重传情况时的RTO设置要采用一种修正处理。当超时发生时,暂时将RTO直接翻倍,当不再发生超时时再重新按照式(5-1)~式(5-3)计算RTO。这样可以保证时延在网络剧变时迅速适应,也可不让短暂的时延抖动产生大的影响。
4.快速重传
为发送的数据设置编号、做接收确认、做超时重传,这些是可靠传输的基本要素。另外,还可以提供一种叫作“快速重传”的机制来作为超时重传的补充。利用快速重传,当数据段传输出错时,可以不必等待超时就能及时发现问题,从而可以更早地重新传递传输出错的数据段。
参照表5-4的内容。在这个表所列的内容中,第3个数据段传输错误,接收方在接收到第4、第5、第6数据段时,又发送了对第2个数据段的确认。如此,接收方就可以收到四次对第2个数据段的确认。当对一个数据段的多次确认发生时,很可能就意味着,紧接着它后面的数据段出现了错误。在表5-4中,确认号201被多次接收到,以201开始的数据段正是丢失的数据段。
当出现多少次重复确认时可以认为发生了数据段丢失呢?当然,出现的重复确认越多,对此就越肯定,但人们肯定希望用尽量少的重复确认来判定数据段丢失。两次重复确认有较大概率是乱序到达造成的,四次重复确认有较大概率是丢包造成的。在实际操作中,是以三次重复的确认来判定数据段丢失,这种现象又被称为三重ACK(3-ACK)事件。当发生3-ACK事件时,TCP处理程序不必等待超时发生,便可直接重传数据段。这样可以提高协议应对传输错误的能力。
3-ACK事件是一种很有用的标志性事件,在网络品质不断进步的今天,它的发生表明了网络出现了某种不太严重的问题。关于这个标志性事件以及应对它的相关措施,后面介绍拥塞控制时还会再次讲到。
表5-4给出的传输情况太过简单化。在实际工作中,发送窗口中的数据可以组成上百甚至几十万个数据段。网络的带宽也让我们可以快捷地发送数据段,即使用现在看起来比较慢的(10 Mb/s)带宽,发送MSS约为1 KB的数据段,每秒可以发送的数据段也可以达到数以千计。国内网络比较典型的几十到上百毫秒的往返时间里,也足以发送几十、上百的数据段。当收到窗口中最左侧数据段的确认(这也意味着窗口将会滑动,这部分数据要离开发送窗口了),或者当它发生3-ACK事件时,它后面可能至少有几十或上百个数据段已经发出——这些数据段或者数据段的接收确认尚在网络传输中。
在此,对于3-ACK事件,可以给出一个更接近实际情况的表格。给出这样的例子,不仅是为了说明3-ACK事件本身,更希望能够帮助读者弥合对书本上简单例子与网络实际情况理解的鸿沟,其具体情况见表5-6。表格中只列出了发送方的情况,在发送了73个数据段后,发送方发现234号数据段的3-ACK重传。
表5-6 更接近实际运行情况的3-ACK事件
5.选择确认
对于表5-6给出的例子要注意一下,在234号数据段丢失后的一段时间里,接收方只能对233号数据段截止的数据进行确认,确认序号只能是50571。虽然发送方在接收到3个重复确认后重传了数据段,但仍然有70个同样的确认会陆续到来。在接收方对重传234号数据段的回应(确认序号92151)到达发送方之前,发送方对235~306号数据段的接收情况一无所知,这会影响到发送方对数据传输情况的判断,从而导致重发这些已经被成功接收的数据。为妥善解决这样的问题,TCP中加入了选择确认的功能。
选择确认允许接收方通知发送方其所有正确接收的数据块。无论这些数据块的前面是否有因为乱序或者错误而形成的数据空洞。表5-6给出的例子里,接收方的TCP处理程序至少在发送了3-ACK之后应该适时采用选择确认向发送方通告接收情况。
若要使用选择确认,就要在数据段首部的选项中加入选择确认字段。要进行选择确认,只发送一个字节的编号是不够用的,其需要给出正确接收的数据块的起始和终止两个字节编号。
当使用选择确认时,TCP首部中的确认序号字段的功能和意义没有改变,在表5-6描述的例子中,该字段的取值应该还在一段时间里保存着50571的取值。这样的处理方式可以兼容那些不支持选择确认的TCP处理程序。当然,目前多数系统的TCP处理程序都支持选择确认功能。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。