首页 > 代码库 > TCP 协议难点汇总

TCP 协议难点汇总

本文不会完整的介绍TCP,只有在涉及到的时候随便提一下。不适合对TCP整个流程和框架没有了解过的人阅读。

    1  TCP 四次挥手中的TIME_WAIT状态的意义何在

下图四次挥手的一个大体的流程

   

我们发现在A发送完一个最后一个ACK后,B一收到这个ACK就证明关闭请求已经被确认了,所以可以直接关闭.但是这时候A怎么知道这个ACK是否已经到达了对端呢?这个时候为了保证双方能够正常关闭,就引入了一个MSL时间,MSL定义

一个电磁波信号在地球最大的存活时间  . 所以该ACK存活一个MSL时间,然后我们假设该ACK丢失了,那么B在超时机制下会重新发送一个FIN,那么这个FIN 也要一个MSL时间后才从地球上消失,所以为了保证A的新连接不会被延迟的FIN终结掉,ATIME_WAIT阶段要等待2MSL时间


2  每个TCP连接实现单一计时器的可行性

我们知道对于每个数据报文都会有一个超时时间,在超时时间内如果没有收到一个ACK的话,那么我们就需要进行重传了,最简单的实现就是对于每个数据报都启动一个定时器,不过这样子有个很大的弊端就是太耗内存了,数据一大肯定计算器顶不顺。所以,现在业界默认都是按照每个TCP的重传计时由单一计时器来实现的

 

单一计时器必须实现的2点:

key1 : 每一个报文在长期收不到确认都必须可以超时;

Key2 : 这个长期收不到中长期不能和测量的RTT相隔太远。

RFC2988定义一套很简单的原则:

a.发送TCP分段时,如果还没有重传定时器开启,那么开启它。

b.发送TCP分段时,如果已经有重传定时器开启,不再开启它。

c.收到一个非冗余ACK时,如果有数据在传输中,重新开启重传定时器。

d.收到一个非冗余ACK时,如果没有数据在传输中,则关闭重传定时器。

下图是按照RFC定义的粗略的画了一下流程

 从图中我们看到,首先发送数据1的时候,发现没有定时器,所以就开启了定时器(RFC a),再发数据2的时候,定时器已经存在了,所以不再启动定时器(RFC b)。在RTO_1时间内,如果没有ACK到来,那么就要启动重传机制了。 

情景一: 假设数据ackRTO_1快要结束的时候到来,那么由于这个是非冗余的ack,所以要重置计时器(RFC c),那么数据2的超时重传时间要多少呢,如图所示接近于RTO_1 + RTO_2 ,差不多是2倍的RTT时间。如果我们按照这种最坏的设想(数据报3,4都在RTO快到的时候才收到ACK)推算下去,假设发送数据1,2,3,4的时间分别是T1,T2,T3,T4

数据的超时时间: time1 = RTO_1

数据的超时时间: time2 = RTO_1 + RTO_2  -T2 - T1) 

数据3的超时时间:  time3 = RTO_1 + RTO_2 + RTO3 - T3 - T1) 

数据4的超时时间:  time4 = RTO_1 + RTO_2 + RTO3 + RTO4  -T4 - T1) 

RTT(往返时间)的计算公式为:

   

那么在数据1,2,3,4所处的不同阶段的RTT值分配以RTT1RTT2 .... 来表示
RTT1 = N     #N代表初始值

RTT2 = RTT1 + g(RTO1 + RTO 2 - (T2 - T1)) = RTT1 + g time2

RTT3 = RTT2 + g timer3

RTT4 = RTT3 + g time4

    我们会发现其实每个RTT 都与当时的time是紧密联系的,所以基本上满足Key_2

情景二

这种情况基本上是情况一的调转过来,新的超时时间如下:

数据的超时时间: time1 = RTO_1 + T4 - T1

数据的超时时间: time2 = RTO_1 + RTO_2  -T3 - T1) 

数据2的超时时间:  time3 = RTO_1 + RTO_2 + RTO3 - T2 - T1) 

数据1的超时时间:  time4 = RTO_1 + RTO_2 + RTO3 + RTO4 

在这种情况里其实会引入一个机制---快速重传,因为TCPACK机制是按照接收方接受窗口中已接受到的数据中最大序号(max_seq)来填写ack值:ack = max_seq + 1,所以当B接受到数据4,3,2的时候,ack值都是数据1的第一个字节序号,只要连续三次重复ack同个数据后,就会启动快速重传机制,这时候数据1就不等超时时间了,直接就进行重发。

还有一个问题就是,由于定时器和数据报之间是一种一对多的关系,那么如果超时了,

我要如何区分是哪个数据报超时了?

在一个发送窗口中有2种数据,一种是已经发送出去但是还没收到确认的数据,一种是还未发送的数据。上图中2个指针分别指示这两种数据,左侧的指针还标识着一个发送窗口的左边界,而第二个指针指示着下个要发送的数据位置。按照这个图来看,我们很容易就可以发现,如果过了超时时间了,那么要重传的一定就是左侧的指针指向的位置,因为如果该元素收到ACK后,那么必然会像161一样随着发送窗口的滑动而不在这个窗口范围内。

 

    3 接受窗口为0时的处理方案

TCP的头部里面可以看到有2个字节的字段来标识窗口大小。

当接收窗口的值为0的时候,那么这时候发送端就不能再发数据,即使发了数据也会被扔掉。当接收窗口不为0的时候,接收端会发送一个报文来告诉发送端当前新的窗口值,但是考虑如果发送端的这个通告报文丢失了,那么发送端以为接收端的窗口值为0,接收端以为发送端没有数据,那么就会造成无限等待的死锁状况发生。所以TCP为了防止这种死锁状况,设定了一个坚持定时器persist timer)规定在这种情况下发送端按无限次的指数避让间隔发出窗口探测包(1字节),TCP收端立刻确认并更新通知窗口


糊涂窗口的场景和解决方案

假设接收窗口为0,接收端的处理程序反应很慢,每次只处理一个字节,如果现在就发送一个报文通知发送端现在接收窗口=1,那么按照定义来看,这时候发送端要发送一个“特殊”的报文即“至少20字节的头部 + 1字节的数据”,如果按照这样的趋势进行交互下去无疑会浪费很多的带宽,这就是糊涂窗口的症状。

解决方案: 当接受窗口再次达到MSS(最大报文长度)的一半大小的时候才发送一个报文来通告这个新的窗口值。在这个期间对发送端的探测报文都回应窗口值为0.