计算机网络学习笔记 ---- TCP重传机制、滑动窗口、流量控制、拥塞控制

TCP通过序列号、确认应答以及各种重传策略确保数据可靠性,如超时重传、快速重传、SACK和D-SACK。滑动窗口机制允许连续发送数据而无需等待每个包的确认,提高效率。流量控制避免发送方过快导致接收方无法处理,而拥塞控制则通过慢启动、拥塞避免和快速恢复算法防止网络拥塞。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 重传机制

TCP 实现可靠传输的方式之一,是通过序列号与确认应答

常见的重传机制:

超时重传
快速重传
SACK
D-SACK

1.1 超时重传

TCP会在数据包丢失确认应答丢失两种情况发生超时重传:

RTT(Round-Trip Time 往返时延):数据发送时刻到接收到确认的时刻的差值,也就是包的往返时间

超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。
在这里插入图片描述

当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

超时重传时间 RTO 的值应该略大于报文往返 RTT 的值。
在这里插入图片描述
每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。

1.2 快速重传

不以时间为驱动,而是以数据驱动重传

在这里插入图片描述
在上图,发送方发出了 1,2,3,4,5 份数据:

· 第一份 Seq1 先送到了,于是就 Ack 回 2;
· 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;
· 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
· 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。
· 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。

快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

但会存在 重传一个or所有 的问题。

1.3 SACK

SACK( Selective Acknowledgment), 选择性确认。

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,
它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,
知道了这些信息,就可以只重传丢失的数据。

如果要支持 SACK,必须双方都要支持。在 Linux 下,可以通过 net.ipv4.tcp_sack 参数打开这个功能(Linux 2.4 后默认打开)。

1.4 D-SACK

Duplicate SACK 又称 D-SACK,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。

情况一:ACK丢包
在这里插入图片描述

· 「接收方」发给「发送方」的两个 ACK 确认应答都丢失了,所以发送方超时后,重传第一个数据包(3000 ~ 3499)
· 于是「接收方」发现数据是重复收到的,于是回了一个 SACK = 3000~3500,告诉「发送方」 3000~3500 的数据早已被接收了,因为 ACK 都到了 4000 了,已	经意味着 4000 之前的所有数据都已收到,所以这个 SACK 就代表着 D-SACK。
· 这样「发送方」就知道了,数据没有丢,是「接收方」的 ACK 确认报文丢了。

情况二:网络延时
在这里插入图片描述

· 数据包(1000~1499) 被网络延迟了,导致「发送方」没有收到 Ack 1500 的确认报文。
· 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包(1000~1499)又到了「接收方」;
· 所以「接收方」回了一个 SACK=1000~1500,因为 ACK 已经到了 3000,所以这个 SACK 是 D-SACK,表示收到了重复的包。
· 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而是因为网络延迟了。

在 Linux 下可以通过 net.ipv4.tcp_dsack 参数开启/关闭这个功能(Linux 2.4 后默认打开)。

2 滑动窗口

为每个数据包确认应答的缺点:数据包的往返时间越长,通信的效率就越低。引入窗口概念。

窗口大小:无需等待确认应答,而可以继续发送数据的最大值。

窗口实际是一个缓存空间。发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

当前的确认应答报文丢失,可通过下一个确认应答进行确认,只要发送方收到了下一次确认应答,就意味着之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答

通常窗口的大小是由接收方的窗口大小来决定的。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

2.1 发送方的滑动窗口

在这里插入图片描述
TCP 滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)。

· SND.WND:表示发送窗口的大小(大小是由接收方指定的);

· SND.UNA(Send Unacknoleged):是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。

· SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。

· 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

可用窗口大小 = SND.WND -(SND.NXT - SND.UNA)

2.2 接收方的滑动窗口

在这里插入图片描述
其中三个接收部分,使用两个指针进行划分:

· RCV.WND:表示接收窗口的大小,它会通告给发送方。

· RCV.NXT:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。

· 指向 #4 的第一个字节是个相对指针,它需要 RCV.NXT 指针加上 RCV.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

接收窗口的大小是约等于发送窗口的大小的。

因为滑动窗口并不是一成不变的。
	比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。
	那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。
	那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。

3 流量控制

TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量。

3.1 操作系统缓冲区与滑动窗口的关系

发送窗口和接收窗口中所存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整。

先减少缓存,再收缩窗口,就会出现丢包的现象。

为了防止这种情况发生,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。

3.2 窗口关闭

如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。

窗口关闭潜在的危险

接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的。当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,则会造成死锁现象
在这里插入图片描述

如何解决窗口关闭时潜在的死锁现象?

TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。

如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
在这里插入图片描述

3.3 糊涂窗口综合症

如果接收方太忙了,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。
到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症。

糊涂窗口综合症的现象是可以发生在发送方和接收方:

· 接收方可以通告一个小的窗口
· 而发送方可以发送小数据

如何让接受方不通告小窗口?

当「窗口大小」小于 min( MSS,缓存空间/2 ) ,也就是小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0,也就阻止了发送方再发数据过来。	
等窗口大小 >= MSS,或者接收方缓存空间有一半可以使用,就可以把窗口打开让发送方发送数据过来。

怎么让发送方避免发送小数据?

使用 Nagle 算法,该算法的思路是延时处理,只有满足下面两个条件中的任意一个条件,才可以发送数据:

条件一:要等到窗口大小 >= MSS 并且 数据大小 >= MSS;
条件二:收到之前发送数据的 ack 回包;

接收方得满足「不通告小窗口给发送方」+ 发送方开启 Nagle 算法,才能避免糊涂窗口综合症。

Nagle 算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如,telnet 或 ssh 这样的交互性比较强的程序,则需要关闭 Nagle 算法。可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭)
在这里插入图片描述

4 拥塞控制

控制的目的就是避免「发送方」的数据填满整个网络。

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。
发送窗口的值是swnd = min(cwnd, rwnd),即拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

· 只要网络中没有出现拥塞,cwnd 就会增大;
· 但网络中出现了拥塞,cwnd 就减少;

只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。

拥塞控制主要是四个算法:

· 慢启动
· 拥塞避免
· 拥塞发生
· 快速恢复

4.1 慢启动

规则:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1

慢启动算法变化过程如下:
在这里插入图片描述
发包的个数是指数性的增长。有一个叫慢启动门限 ssthresh (slow start threshold)状态变量来控制发包上限

	当 cwnd < ssthresh 时,使用慢启动算法。
	当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。

4.2 拥塞避免

一般来说 ssthresh 的大小是 65535 字节。

规则:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

拥塞避免算法的变化过程如下图:
在这里插入图片描述

当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。

当触发了重传机制,也就进入了「拥塞发生算法」。

4.3 拥塞发生

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

超时重传
快速重传

发生超时重传的拥塞发生算法

当发生了「超时重传」,则就会使用拥塞发生算法。这个时候,ssthresh 和 cwnd 的值会发生变化:

ssthresh 设为 cwnd/2,
cwnd 重置为 1 (是恢复为 cwnd 初始化值,这里假定 cwnd 初始化值 1)

拥塞发生算法的变化如下图:
在这里插入图片描述

发生快速重传的拥塞发生算法

当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:

· cwnd = cwnd/2 ,也就是设置为原来的一半;
· ssthresh = cwnd;
· 进入快速恢复算法

4.4 快速恢复

快速重传和快速恢复算法一般同时使用

进入快速恢复算法如下:

拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
重传丢失的数据包;
如果再收到重复的 ACK,那么 cwnd 增加 1;
如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

快速恢复算法的变化过程如下图:
在这里插入图片描述
首先,快速恢复是拥塞发生后慢启动的优化,其首要目的仍然是降低 cwnd 来减缓拥塞,所以必然会出现 cwnd 从大到小的改变。

其次,过程2(cwnd逐渐加1)的存在是为了尽快将丢失的数据包发给目标,从而解决拥塞的根本问题(三次相同的 ACK 导致的快速重传),所以这一过程中 cwnd 反而是逐渐增大的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Swing_zzZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值