3.2.3.数据顺序性

基本上传输可靠性是靠序列号实现的。

● 疑难杂症7:确认号和超时重传

确认号是一个很诡异的东西,因为TCP的发送端对于发送出去的一个数据序列,它只要收到一个确认号就认为确认号前面的数据都被收到了,即使前面的某个确认号丢失了,也就是说,发送端只认最后一个确认号。这是合理的,因为确认号是接收端发出的,接收端只确认按序到达的最后一个TCP分段。

另外,发送端重发了一个TCP报文并且接收到该TCP分段的确认号,并不能说明这个重发的报文被接收了,也可能是数据早就被接收了,只是由于其ACK丢失或者其ACK延迟到达导致了超时。值得说明的是,接收端会丢弃任何重复的数据,即使丢弃了重复的数据,其ACK还是会照发不误的。

标准的早期TCP实现为,只要一个TCP分段丢失,即使后面的TCP分段都被完整收到,发送端还是会重传从丢失分段开始的所有报文,这就会导致一个问题,那就是重传风暴,一个分段丢失,引起大量的重传。这种风暴实则不必要的,因为大多数的TCP实现中,接收端已经缓存了乱序的分段,这些被重传的丢失分段之后的分段到达接收端之后,很大的可能性是被丢弃。关于这一点在拥塞控制被引入之后还会提及(问题先述为快:本来报文丢失导致超时就说明网络很可能已然拥塞,重传风暴只能加重其拥塞程度)。

● 疑难杂症8:乱序数据缓存以及选择确认

TCP是保证数据顺序的,但是并不意味着它总是会丢弃乱序的TCP分段,具体会不会丢弃是和具体实现相关的,RFC建议如果内存允许,还是要缓存这些乱序到来的分段,然后实现一种机制等到可以拼接成一个按序序列的时候将缓存的分段拼接,这就类似于IP协议中的分片一样,但是由于IP数据报是不确认的,因此IP协议的实现必须缓存收到的任何分片而不能将其丢弃,因为丢弃了一个IP分片,它就再也不会到来了。

现在,TCP实现了一种称为选择确认的方式,接收端会显式告诉发送端需要重传哪些分段而不需要重传哪些分段。这无疑避免了重传风暴。

● 疑难杂症9:TCP序列号的回绕的问题

TCP的序列号回绕会引起很多的问题,比如序列号为s的分段发出之后,m秒后,序列号比s小的序列号为j的分段发出,只不过此时的j比上一个s多了一圈,这就是回绕问题,那么如果这后一个分段到达接收端,这就会引发彻底乱序-本来j该在s后面,结果反而到达前面了,这种乱序是TCP协议检查不出来的。我们仔细想一下,这种情况确实会发生,数据分段并不是一个字节一个字节发送出去的,如果存在一个速率为1Gbps的网络,TCP发送端1秒会发送125MB的数据,32位的序列号空间能传输2的32次方个字节,也就是说32秒左右就会发生回绕,我们知道这个值远小于MSL值,因此会发生的。

有个细节可能会引起误会,那就是TCP的窗口大小空间是序列号空间的一半,这样恰好在满载情况下,数据能填满发送窗口和接收窗口,序列号空间正好够用。然而事实上,TCP的初始序列号并不是从0开始的,而是随机产生的(当然要辅助一些更精妙的算法),因此如果初始序列号比较接近2的32次方,那么很快就会回绕。

当然,如今可以用时间戳选项来辅助作为序列号的一个识别的部分,接收端遇到回绕的情况,需要比较时间戳,我们知道,时间戳是单调递增的,虽然也会回绕,然而回绕时间却要长很多。这只是一种策略,在此不详谈。还有一个很现实的问题,理论上序列号会回绕,但是实际上,有多少TCP的端点主机直接架设在1G的网络线缆两端并且接收方和发送方的窗口还能恰好被同时填满。另外,就算发生了回绕,也不是一件特别的事情,回绕在计算机里面太常见了,只需要能识别出来即可解决,对于TCP的序列号而言,在高速网络(点对点网络或者以太网)的两端,数据发生乱序的可能性很小,因此当收到一个序列号突然变为0或者终止序列号小于起始序列号的情况后,很容易辨别出来,只需要和前一个确认的分段比较即可,如果在一个经过路由器的网络两端,会引发IP数据报的顺序重排,对于TCP而言,虽然还会发生回绕,也会慢得多,且考虑到拥塞窗口(目前还没有引入)一般不会太大,窗口也很难被填满到65536。

3.2.4.端到端的流量控制

端到端的流量控制使用滑动窗口来实现。滑动窗口的原理非常简单,基本就是一个生产者/消费者模型

● 疑难杂症10:流量控制的真实意义

很多人以为流量控制会很有效的协调两端的流量匹配,确实是这样,但是如果你考虑到网络的利用率问题,TCP的流量控制机制就不那么完美了,造成这种局面的原因在于,滑动窗口只是限制了最大发送的数据,却没有限制最小发送的数据,结果导致一些很小的数据被封装成TCP分段,报文协议头所占的比例过于大,造成网络利用率下降,这就引出了接下来的内容,那就是端到端意义的TCP协议效率。

终于到了阐述问题的时候了,以上的TCP协议实现的非常简单,这也是TCP的标准实现,然而很快我们就会发现各种各样的问题。这些问题导致了标准化协会对TCP协议进行了大量的修补,这些修补杂糅在一起让人们有些云里雾里,不知所措。本文档就旨在分离这些杂乱的情况,实际上,根据RFC,这些杂乱的情况都是可以找到其单独的发展轨迹的。

4.端到端意义上的TCP协议效率

4.1.三个问题以及解决

◆ 问题1描述:接收端处理慢,导致接收窗口被填满

这明显是速率不匹配引发的问题,然而即使速率不匹配,只要滑动窗口能协调好它们的速率就好,要快都快,要慢都慢,事实上滑动窗口在这一点上做的很好。但是如果我们不得不从效率上来考虑问题的话,事实就不那么乐观了。考虑此时接收窗口已然被填满,慢速的应用程序慢腾腾的读取了一个字节,空出一个位置,然后通告给TCP的发送端,发送端得知空出一个位置,马上发出一个字节,又将接收端填满,然后接收应用程序又一次慢腾腾…这就是糊涂窗口综合症,一个大多数人都很熟悉的词。这个问题极大的浪费了网络带宽,降低了网络利用率。好比从大同拉100吨煤到北京需要一辆车,拉1Kg煤到北京也需要一辆车(超级夸张的一个例子,请不要相信),但是一辆车开到北京的开销是一定的…

问题1解决:窗口通告

对于问题1,很显然问题出在接收端,我们没有办法限制发送端不发送小分段,但是却可以限制接收端通告小窗口,这是合理的,这并不影响应用程序,此时经典的延迟/吞吐量反比律将不再适用,因为接收窗口是满的,其空出一半空间表示还有一半空间有数据没有被应用读取,和其空出一个字节的空间的效果是一样的,因此可以限制接收端当窗口为0时,直接通告给发送端以阻止其继续发送数据,只有当其接收窗口再次达到MSS的一半大小的时候才通告一个不为0的窗口,此前对于所有的发送端的窗口probe分段(用于探测接收端窗口大小的probe分段,由TCP标准规定),全部通告窗口为0,这样发送端在收到窗口不为0的通告,那么肯定是一个比较大的窗口,因此发送端可以一次性发出一个很大的TCP分段,包含大量数据,也即拉了好几十吨的煤到北京,而不是只拉了几公斤。

即,限制窗口通告时机,解决糊涂窗口综合症


相关内容