TCP流量控制与拥塞控制

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,则会产生超时重传,发送方会使用慢启动算法控制数据发送速度。

相关推荐
I'm a winner8 小时前
【IP核】 Xilinx FPGA LVDS 高速接口,含验证工程与板级测试用例
tcp/ip·fpga开发·测试用例
Piko61410 小时前
H3C IRF2 堆叠实战:打造高可靠核心交换网络
运维·网络·笔记
茯苓gao13 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
万联WANFLOW13 小时前
SD-WAN 控制平面高可用怎么做?SDWAN 控制器挂了,全网会发生什么
运维·网络·分布式·架构
酱学编程14 小时前
【从零到一实现一个 AI Agent 框架 · 第四篇】04. 任务规划:拆解复杂目标 -
服务器·网络·数据库·人工智能
风行南方15 小时前
密码学之分组密码
网络·密码学
艾莉丝努力练剑16 小时前
OpenCode AI 编程:Ubuntu 24.04 环境安装与使用指南
linux·服务器·网络·人工智能·tcp/ip·ubuntu
谷雪_65817 小时前
Linux 网络命名空间:从内核原理到企业级容器网络架构全景实战
linux·网络·架构
liulilittle17 小时前
KCC 拥塞控制算法缺陷修复记录(26/07/05)
网络·tcp/ip·计算机网络·c·信息与通信·tcp·通信
Tian_Hang17 小时前
Eclipse Ditto 的权限策略
java·服务器·前端·网络·ide·ubuntu·eclipse