1.滑动窗口
TCP使用滑动窗口机制来控制数据发送速度。滑动窗口机制的实现方式是发送方发出的数据,仍然保留在发送缓存中,收到接收方的确认应答后才从发送缓存中丢弃。
1.1 发送窗口
滑动窗口的大小由发送方的发送窗口swnd确定,表示发送方不必等待接收方确认可以连续发送的数据量。
发送方的发送窗口swnd取接收方的接收窗口rwnd和发送方的拥塞窗口cwnd两者中的较小值,即swnd = min(rwnd, cwnd)。接收窗口rwnd和拥塞窗口cwnd是不断变化,动态调整的,所以发送窗口swnd也是不断变化的。
接收窗口rwnd是接收方在确认数据时向发送方通告的值,表示接收方当前可用的接收缓存大小。TCP使用接收窗口rwnd实现流量控制,避免发送方发送的数据量超出接收方的处理能力。
拥塞窗口cwnd是发送方自己估算出的值,表示当前网络状况允许的合适数据发送速度。TCP使用拥塞窗口cwnd实现拥塞控制,避免发送方发送的数据量超过网络的处理能力。
1.2 最大报文段
最大报文段MSS = MTU -- TCP头部 -- IP头部 = 1500 -- 20 -- 20 = 1460。
TCP 连接建立时,通信双方建立连接时在SYN报文的选项字段中交换各自支持的MSS 值,最终取双方都支持的较小值作为实际使用的 MSS。
通信双方协商MSS的好处:
(1)可以避免 IP 分片,TCP 通过MSS控制数据段大小,确保数据在传输前就被分割成合适的块,避免 IP 层分片重组带来的性能损耗。
(2)可以优化传输效率。太小的MSS会增加协议头比例,降低有效数据传输效率。太大的 MSS可能导致数据包在某些网络设备上被丢弃。
(3)在带宽充足的网络中,适当增大MSS可显著提升传输性能,提高吞吐量。
2. 流量控制
发送方根据接收方确认的接收窗口rwnd获知接收方的数据处理能力,实现流量控制。
2.1 接收窗口rwnd
TCP头部包含窗口大小字段。接收方收到数据后,会在确认数据的应答ACK报文中通告自己的接收窗口rwnd,告知发送方自己当前可用的缓存大小,作为发送方控制数据发送速度的一个参考因素。
2.2 窗口扩大因子
TCP头部接收窗口大小只有16位,最大值是64k,真实的接收缓存可能要大得多,收发双方可以使用窗口扩大因子选项来协商实际窗口的放大倍数。
窗口扩大因子必须在建立TCP连接时协商设置,连接建立之后不可更改,连接双方使用SYN和SYN-ACK报文携带TCP头部窗口扩大因子选项,互相告知对方自己的接收窗口大小。
实际接收窗口大小 = TCP头部接收窗口大小 * 窗口扩大因子
2.3 窗口收缩
当服务端系统资源非常紧张的时候,操作系统可能会直接减少接收缓冲区大小,若应用程序无法及时读取缓存数据,就会出现数据丢失。为了避免数据丢失,TCP规定不允许同时减少接收缓存又收缩窗口,而是采用先收缩窗口,过段时间再减少缓存。
2.4 窗口关闭
如果没有可用缓存,接收方会在ACK报文中把接收窗口大小设置为0,阻止发送方继续发送数据,这就是窗口关闭。
当发生窗口关闭时,接收方处理完数据后,会在ACK报文中把接收窗口大小设置为非0,通知发送方可以恢复数据发送。
如果通告窗口变化的ACK 报文在网络中丢失了,发送方就无法继续发送数据。为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要TCP连接一方收到对方的窗口关闭通知,就启动持续计时器。
如果持续计时器超时,发送方会发送窗口探测报文,接收方在应答这个探测报文时,给出自己现在的接收窗口大小。
窗口探测的次数一般为3,每次大约30-60 秒,不同的实现可能会不一样。如果3次过后接收窗口还是0的话,有的TCP实现就会发RST报文来重置连接。
2.5 糊涂窗口综合症
如果接收方来不及取走接收缓存里的数据,就会导致发送方的发送窗口越来越小。如果接收方应用层从接收缓存中读取几个字节后,协议栈立即通知发送方现在有几个字节的窗口,发送方立即发送几个字节的数据,就会出现糊涂窗口综合症。
出现糊涂窗口综合症的原因是:接收方通告一个小窗口,发送方发送小数据包。
要解决糊涂窗口综合症,就要让接收方不通告小窗口,让发送方避免发送小数据包。
2.5.1 接收方不通告小窗口
当窗口小于min(MSS, 接收缓存/2 ) ,也就是小于MSS与接收缓存一半中的最小值时,就会向发送方通告窗口为0,阻止发送方继续发数据。等接收方从缓存中读取一些数据后,窗口大小 >= MSS,或者接收方接收缓存有一半可以使用,就可以开启窗口让发送方继续发送数据。
2.5.2 发送方不发送小数据包
发送方可以使用Nagle算法来控制尽量不发送小数据包。
启用Nagle算法,只有满足两个条件中的任意一个后,协议栈才继续发送数据:
(1)数据量大小 >= MSS,且窗口大小 >= MSS;
(2)收到接收方对上个小数据包的ACK,即最多只允许有一个未被确认的小数据包(数据量< MSS)。
Nagle算法默认是打开的,实时交互式应用需要关闭Nagle算法,如telnet或ssh等。可以通过为套接字设置TCP_NODELAY选项来关闭Nagle算法。
3.拥塞控制
发送方根据网络状况动态估算可以发送的数据量,实现拥塞控制。
发送方根据拥塞窗口cwnd和慢启动阈值ssthresh(slow start threshold)之间的数值关系来选择不同的拥塞控制算法。
cwnd < ssthresh时,使用慢启动算法。
cwnd == ssthresh时,既可以使用慢启动算法,也可以使用拥塞避免算法。
cwnd > ssthresh时,使用拥塞避免算法。
拥塞控制算法的切换过程入下图所示:


3.1 慢启动
TCP连接刚建立时,使用慢启动算法,先少发点数据试探,每经历一轮RTT,发送数据量就指数性快速增长,直到达到慢启动阈值ssthresh。
TCP连接刚建立时,拥塞窗口cwnd的默认初始值一般是几个MSS,假设为1个MSS,慢启动阈值ssthresh的默认初始值一般是65536字节。
慢启动算法的进入条件是:cwnd < ssthresh。
慢启动算法只改变cwnd的值,不改变ssthresh的值。
慢启动算法改变cwnd的规则是:发送方每收到一个ACK,cwnd增加1个MSS。
若当前cwnd=N * MSS,一轮RTT会连续发出N个MSS数据包(假设cwnd < rwnd,发送方不必等接收方应答ACK即可连续发送的数据量是cwnd),相应地也会连续收到接收方应答的N个ACK,接收方收到N个数据包后只合并应答1个ACK的情况按应答N个ACK算。也即一轮RTT后cwnd = cwnd + N * MSS = N * MSS + N * MSS = 2* N * MSS = 2cwnd。相当于每经历一轮RTT,cwnd增加1倍,所以慢启动算法的数据发送速度是指数性快速增长的。
3.2 拥塞避免
当发送数据量达到慢启动阈值ssthresh后,发送方会启用拥塞避免算法,发送数据量不再指数性快速增长,而是线性缓慢增长,这样可以防止短时间内发送大量数据造成网络拥塞。
拥塞避免算法的进入条件是:cwnd > ssthresh。
拥塞避免算法只改变cwnd的值,不改变ssthresh的值。
拥塞避免算法改变cwnd的规则是:发送方每收到一个ACK ,cwnd增加(1/cwnd)* MSS。
若当前cwnd=N * MSS,一轮RTT会连续发出N个MSS数据包(假设cwnd < rwnd,发送方不必等接收方应答ACK即可连续发送的数据量是cwnd),相应地也会连续收到接收方应答的N个ACK,接收方收到N个数据包后只合并应答1个ACK的情况按应答N个ACK算。也即一轮RTT后cwnd = cwnd + N * (1/cwnd) * MSS = N * MSS + N * (1/N) * MSS = (N+1) * MSS。相当于每经历一轮RTT,cwnd增加1个MSS,所以拥塞避免算法的数据发送速度是线性缓慢增长的。
3.3 拥塞发生
发送方发出的数据丢失,或者接收方应答的ACK报文丢失,导致发送方没有及时收到接收方的应答ACK,会让发送方认定数据传输超时,启动超时重传。
发生超时重传后,发送方会使用拥塞发生算法改变ssthresh和cwnd的值,具体改变为:
(1)ssthresh设为当前cwnd的一半,即ssthresh = cwnd/2。
(2)cwnd重置为初始值,假设为1个MSS。
使用拥塞发生算法后,ssthresh和cwnd迅速减小,数据发送速度会迅速下降。
使用拥塞发生算法改变ssthresh和cwnd,会导致cwnd 小于 ssthresh,此后,发送方会重新使用慢启动算法控制数据发送速度,如下图所示。
3.4 快速恢复
3.4.1 快速重传
发送方连续发出多包数据,若前边部分数据丢失,后续每次发送的数据到达接收方后,接收方应答的ACK报文TCP头部填入的期望序列号都是前边丢失部分数据的起始序列号。
发送方连续收到3个期望序列号相同的ACK后,可以获知前边部分数据丢失,会立即重传丢失的部分数据,不必等待超时,这就是快速重传。
快速重传能够避免等待 RTO 超时带来的延迟,大大缩短了丢包后的恢复时间,适用于丢包率较高但延迟较低的网络环境。
3.4.2 快速恢复
因为发送方既然能收到3个重传的ACK,说明发送方发送的数据大部分到达了接收方,丢包率不高,也说明网络拥塞并不严重,不需要把数据发送速度降得过低。
发生快速重传后,发送方会使用快速恢复算法改变cwnd和ssthresh的值,具体改变为:
(1)ssthresh设为当前cwnd的一半,即ssthresh = cwnd/2。
(2)cwnd = ssthresh + 3*MSS。
发送方收到3个重复的ACK,所以cwnd在ssthresh的基础上加3个MSS。
使用快速恢复算法改变ssthresh和cwnd后,会导致cwnd大于ssthresh。
若快速重传的数据及时收到接收方的应答ACK,则发送方会使用拥塞避免算法控制数据发送速度。
若快速重传的数据超时仍未收到接收方的应答ACK,则会产生超时重传,发送方会使用慢启动算法控制数据发送速度。