写在前面
该blog只针对流量控制和拥塞控制,有关TCP三次握手和其他文中提及知识点不再赘述。
网络上有关该知识点的内容很多,我只希望我的语言可以帮助到一些人理解。
流量控制(滑动窗口机制)
在网络通信中,我们都会希望数据的传输效率更高一些,让通信更加流畅。但是,如果发送方的发送速率太快,可能会导致接收方来不及接收和处理数据。
让我们来思考这样一种现象,如果发送方发送的数据量太多太快,导致接收方来不及接收和处理,那么无法处理的数据包只能丢弃在网络中,那么会造成极大的网络资源浪费。因此,我们需要一种机制控制发送方的传输速率——滑动窗口。
注意,流量控制是一种接收方针对发送方来讲的机制。
现在我们假定数据流向是单向的,也就是说发送方(A)只负责发送数据,不负责处理和接受数据,接收方(B)只负责处理和接收数据,并且对接收到的数据进行应答(ACK)。
在A和B建立TCP连接时,B会将自己的接收窗口大小(RWND,假定为400字节)告知A,A会将自己的发送窗口大小也设置为400,三次握手连接建立完成后,双方进入ESTABLISHED(数据通信状态)。
我们假定A有900字节的待发送数据,且每次会发送TCP报文段包含数据100字节,并且A将自己发送窗口大小设置为400字节,如图(蓝色框为滑动窗口)所示:
A向B连续发送3个TCP报文段,每一个报文段都包含了100字节的数据,其中第3个TCP报文段因某种网络原因丢失。
紧接着,A收到了来自B的应答报文段,了解到B的接收窗口大小(rwnd)为300,因为没有收到201-300的确认报文,因此此时滑动窗口的位置是201-500,并且发送301-400和401-500的TCP报文段给B,如上图所示。当201-300的报文段的超时重传计时器到达时,重新发送201-300的TCP报文段给B,随后收到了B对501以前的所有报文的确认,并且被告知B的滑动窗口大小此时为0(rwnd=0),并且刷新了缓冲区中1-500的数据,并进入等待(因为B的rwnd=0,表明B现在没有能力接收新的数据, 此时如果再发数据,会丢失)。
此时A需要等到B新发送的接收窗口大小更新的数据(rwnd > 0)才能继续发送数据。。。。。。。终于,B意图向A更新自己的rwnd,但是因为网络原因,这个更新报文段丢失,此时将会导致A在等待B的rwnd,B在等待A的新数据,于是出现了死锁局面。如图所示:
为了防止这种意外的发生,我们设置A在收到B的0接收窗口大小(rwnd=0)时会自动启动一个“持续计时器”,当持续计时器timeout时,如果还没有收到来自B的rwnd更新报文段,则会发送一个零窗口探测报文段(携带1字节数据),当B接收到这个TCP报文段后,会给A回复一个rwnd的更新,如果此时rwnd > 0,那么A就可以继续发送数据,如果rwnd=0,则重新启动一个持续计时器,重复上述步骤。
这就是有关流量控制的基本流程和内容,流量控制并不是TCP独有的模式,在链路层也有流量控制的存在,这里就不再赘述。
值得注意的是,实际网络中,发送方一次可以向接收方发送的TCP报文段的大小和数量受到流量控制和拥塞控制或其他因素的共同影响,就流量控制和拥塞控制而言,发送方的发送速率总是收到两者较小一个的影响。
提一嘴
如果已经使用过socket编程的同学都知道,Linux内核会为一个socket套接字维护两个缓冲区,读缓冲和写缓冲。对应到上文中,A和B中都有一个用于发送数据的写缓冲和用于接收数据的读缓冲(这也是为何socket可以进行双向全双工通信的原因)。在上文,因为假设只有A向B发送数据,因此接收窗口大小可以理解为B中socket的读缓冲区,当读缓冲区被占满且B没有拿走里面的数据时,rwnd值就会变为0。