MSS:Maximum Segment Size,TCP一次传输发送的最大数据段长度。
RTT:Round-Trip Time,往返时延,表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。
TCP传输大块数据时,肯定需要进行数据分段,而每个分段所能携带的最大数据就是1个MSS,假设大块数据为100个MSS,那么发送方发送的方式大概有如下两种:
1、 每次发送1个,收到接收方确认后,才发送下1个;
2、 一口气发送100个,然后收到对方一起确认;
显然,方式1中,一个RTT只能处理一个包,这样的传输效率太低了。而方式2看似很美好,实际会存在两个问题,一个是接收方的接收窗口未必能一次性接收这么多数据,另外一个是网络的带宽也不一定足够大,容易出现丢包事故。前一个问题就是标题中的流量控制(Flow control),TCP采用的是滑动窗口机制(Sliding window),后一个问题就是标题中的拥塞控制(Congestion control)。发送方的发送窗口或者说网络传输交互就取决于这两个问题的控制,谁控制的更严格,谁就占据了决定性因素,这也是为什么两者总是一起出现一起被讨论。
流量控制(Flow control):
TCP uses an end-to-end flow control protocol to avoid having the sender send data too fast for the TCP receiver to receive and process it reliably. Having a mechanism for flow control is essential in an environment where machines of diverse network speeds communicate.
TCP使用端到端流量控制协议来避免发送方发送数据太快,以致TCP接收方不能可靠地接收和处理数据。在不同网络速度的机器进行通信的环境中,具有流量控制机制至关重要。
图一
图一为通过Wireshark抓包192.168.2.1和192.168.2.198的交互截图,可以看到有个标记Win,这个标记的含义就是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
[TCP zerowindow]& [TCP window Full]
提到Win标记,就顺便谈一下Wireshark的[TCP zerowindow]和[TCP window Full],当Win=0时,Wireshark就会打上TCP zerowindow,表示缓存区已满,不能再接收数据了。当Wireshark在一个包上打上TCP window Full,就表示这个包的发送方已经把对方声明的接收窗口耗尽了。两者的共同特点都是传输暂停,前者的含义是发送方无法再接收数据,后者表示发送方无法再发送数据。
回过头来再来说滑动窗口。
图二截取自《TCP/IP详解》
图三取自参考资料
图四取自参考资料
图二和图三可以知道接收方会通知发送方当前已接收到的信息和可用窗口信息。
图三和图四可以看出来窗口滑动的过程。
总结:TCP的流量控制由滑动窗口来实现的,滑动窗口控制流量取决于接收方的窗口大小。
拥塞控制(Congestion control):
流量控制是端到端的交互,如果只是局域网内的两台设备交互,我想通过滑动窗口大概能控制得不差,但是实际网络的情况非常复杂,发送方和接收方之间还有路由器和交换机,网络传输线路又复杂,这个时候就需要拥塞控制。
拥塞控制主要有四个算法:慢启动、拥塞避免、快速重传和快速恢复。
慢启动:
讨论慢启动算法先来了解下拥塞窗口的概念,这是慢启动算法为TCP发送方新增的窗口,congestion window,简称cwnd。对应上文,发送方取拥塞窗口和滑动窗口的最小值作为发送上限,即谁严格谁起决定因素。
1、 连接建立开始,发送方不了解网络的情况,cwnd初始化比较小的值,RFC建议2-4个MSS,具体视MSS的大小而定;
If (MSS <= 1095 bytes)
then win <= 4 * MSS;
If (1095 bytes < MSS < 2190 bytes)
then win <= 4380;
If (2190 bytes <= MSS)
then win <= 2 * MSS; 摘自rfc3390.
2、 如果发送出去的包都被ACK,说明还未到达拥塞点,则增加拥塞窗口,RFC建议的是每收到n个ACK,则cwnd新增n个MSS,呈指数关系增长,虽然这个过程看似比较快,但是基数比较低,所以被称为“慢启动”。
拥塞避免:
其实慢启动除了维护了cwnd,还维护了慢启动临界值ssthresh,一般将ssthresh设置为65535字节。在cwnd<=ssthresh时,还是处于慢启动环节,一旦>ssthresh,开始进入拥塞避免。
RFC建议拥塞避免环节,无论一个RTT可以收到多少个ACK,每一次确认都只新增1个MSS,呈线性关系增长,避免快速的触碰到网络拥塞点。
图五取自参考资料
快速重传和快速恢复:
进入拥塞避免之后,最终还是会碰到拥塞点,发送方此时迟迟得不到确认,当然得不到确认也有可能是因为延迟确认导致的。发送方此时决定等待一段时间,如果一段时间后还是得不到确认,就发起重传,这个过程叫做超时重传。从发出原始包到重传该包的时间叫做RTO(Retransmission TimeOut)。
进入超时重传后,RFC建议将cwnd设置为1个MSS,而对于ssthresh,RFC5681建议的是发生拥塞时未被ACK的数据量的1/2,但必须大于等于2个MSS。然后重新进入慢启动环节。超时重传因为需要等待RTO之后才能进入新的恢复环节,所以对网络性能的影响是比较大的。所以各路大神又想到了一个新的方式,看能否无需等待RTO,就发起重传,这种方式叫做快速重传。快速重传规定在收到3个及以上重复ACK时就触发重传,不再进入慢启动环节,然后直接进入拥塞避免,这个就是快速恢复算法。为什么是3个?因为1-2个重复ACK,很有可能是乱序,只有在3个及以上的时候才是有可能丢包了。