计算机网络--传输层--1.2*--TCP--TCP、重传、滑动窗口、流量控制、拥塞控制、显示拥塞通知、路径MTU发现
计算机网络TCP拥塞控制窗口大小变化、重传、滑动窗口、流量控制等
用户空间
- tcp/ip协议簇应用层,由应用层程序员控制
发送缓存- 通过send函数将用户空间发送缓存中的数据发送到内核空间发送缓存中
接收缓存- 通过receive函数将内核空间中的数据取回到用户空间中的接收缓存中
内核空间
- tcp/ip协议簇传输层,右操作系统控制
发送缓存接收缓存
tcp
发送窗口(发送端滑动窗口)接收窗口(接收端滑动窗口)拥塞窗口ACK报文中的window字段
1 重传机制
1.1 超时重传
重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传。
TCP 会在以下两种情况发生超时重传:
- 数据包丢失
- 确认应答丢失

1.1.1 超时时间
我们先来了解一下什么是 RTT (Round-Trip Time 往返时延)
RTT 就是数据从网络一端传送到另一端所需的时间,也就是包的往返时间。
超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。
假设在重传的情况下,超时时间 RTO 「较长或较短」时,会发生什么事情呢?
- 当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
- 当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致
更多的超时,更多的超时导致更多的重发。
精确的测量超时时间 RTO 的值是非常重要的,这可让我们的重传机制更高效。
根据上述的两种情况,我们可以得知,超时重传时间 RTO 的值应该略大于报文往返 RTT 的值。
实际上「报文往返 RTT 的值」是经常变化的,因为我们的网络也是时常变化的。也就因为「报文往返
RTT 的值」 是经常波动变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值。
如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。
也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。
超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢?
于是就可以用「快速重传」机制来解决超时重发的时间等待。
1.2 快速重传
TCP 还有另外一种快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。
快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文 段。
快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。
比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。
根据 TCP 不同的实现,以上两种情况都是有可能的。可见,这是一把双刃剑。
为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。
1.3 SACK 方法
SACK( Selective Acknowledgment 选择性确认)需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将接收方 接受缓存的地图(接受缓存中数据的接受状况 哪些数据接受到了哪些数据没接受到) 发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发
现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。
如果要支持 SACK ,必须双方都要支持。在 Linux 下,可以通过 net.ipv4.tcp_sack 参数打开这个功
能(Linux 2.4 后默认打开)。
1.4 Duplicate SACK
Duplicate SACK 又称 D-SACK ,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。
下面举例两个例子,来说明 D-SACK 的作用。
1.4.1 ACK 丢包

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

- 数据包(1000~1499) 被网络延迟了,导致「发送方」没有收到 Ack 1500 的确认报文。
- 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包(1000~1499)又到了「接收方」;
- 所以「接收方」回了一个 SACK=1000~1500,因为 ACK 已经到了 3000,所以这个 SACK 是 DSACK,表示收到了重复的包。
- 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而
是因为网络延迟了。
可见, D-SACK 有这么几个好处:
- 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
- 可以知道是不是「发送方」的数据包被网络延迟了;
- 可以知道网络中是不是把「发送方」的数据包给复制了;
在 Linux 下可以通过 net.ipv4.tcp_dsack 参数开启/关闭这个功能(Linux 2.4 后默认打开)。
TCP 使用了超时重传和快速重传两种机制来处理丢失的数据包,具体选择哪种机制取决于网络条件和数据包丢失的情况。
超时重传(Timeout Retransmission):当发送方发送数据包后,如果在一定时间内没有收到对应的确认(ACK)信号,就会认为该数据包丢失,并启动超时重传机制。发送方会重新发送丢失的数据包。超时时间通常由TCP的拥塞控制算法动态调整,以适应当前网络的延迟和拥塞情况。
快速重传(Fast Retransmit):快速重传是一种针对连续丢失数据包的优化机制。当发送方收到对同一个序列号的数据包的三个重复确认(即收到三个相同的ACK,表示之前的一个或多个数据包已经丢失)时,它会立即重传该数据包,而不等待超时。这样可以快速恢复丢失的数据包,提高数据传输的效率。
综上所述,TCP使用了超时重传和快速重传两种机制来应对不同情况下的数据包丢失。超时重传适用于长时间没有收到确认信号的情况,而快速重传则适用于连续丢失数据包的情况,以提高数据传输的效率和可靠性。
区别数据包丢失、ACK包丢失、网络延迟三种情况:
- 数据包丢失、网络延迟 都发生在快速重传的情况下。这两种情况ACK包中的ack确认序号都是按序表示下一个要接收的数据; 数据包丢失 时sack表示的范围是从紧挨断开的未接收到的数据后续已接收到的数据算起,ack确认序号与sack差的范围就是丢失的包的范围,如ack=200, sack=300-500,则丢失包的范围是201-299,即也是需要重传的包;网络延迟 时sack表示丢失的包的数据范围,如ack=300,sack=100-200,表示网络发生了延迟。
- ACK包丢失 发生在超时重传情况下
2 滑动窗口
2.1 引入窗口概念的原因
TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。
所以,这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低。
为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。
那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。
窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
假设窗口大小为 3 个 TCP 段,那么发送方就可以「连续发送」 3 个 TCP 段,并且中途若有 ACK
丢失,可以通过「下一个确认应答进行确认」。如下图:
图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方
收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。
2.2 窗口大小
TCP 头里有一个字段叫 Window ,也就是窗口大小。
这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来,这就是流量控制。
所以,通常窗口的大小是由接收方的窗口大小来决定的。
发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。
2.3 发送方的滑动窗口
下图就是发送方缓存的数据,根据处理的情况分成四个部分,其中深蓝色方框是发送窗口,紫色方框是可用窗口:
在下图,当发送方把数据「全部」都一下发送出去后,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了。
在下图,当收到之前发送的数据 32~36 字节的 ACK 确认应答后,如果发送窗口的大小没有变化,则
滑动窗口往右边移动 5 个字节,因为有 5 个字节的数据被应答确认,接下来 52~56 字节又变成了可用窗口,那么后续也就可以发送 52~56 这 5 个字节的数据了。
2.3.1 程序如何表示发送方的四个部分
TCP 滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)。
SND.WND:表示发送窗口的大小(大小是由接收方指定的);SND.UNA:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是
#2 的第一个字节。SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3
的第一个字节。- 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,
就可以指向 #4 的第一个字节了。
那么可用窗口大小的计算就可以是:
可用窗口大 = SND.WND -(SND.NXT - SND.UNA)
2.4 接收方的滑动窗口
接收方的窗口根据处理的情况划分成三个部分:
- #1 + #2 是已成功接收并确认的数据(等待应用进程读取);
- #3 是未收到数据但可以接收的数据;
- #4 未收到数据并不可以接收的数据;

其中三个接收部分,使用两个指针进行划分: RCV.WND:表示接收窗口的大小,它会通告给发送方。RCV.NXT:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的
第一个字节。- 指向 #4 的第一个字节是个相对指针,它需要 RCV.NXT 指针加上 RCV.WND 大小的偏移量,
就可以指向 #4 的第一个字节了。
接收窗口和发送窗口的大小是相等的吗?
并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。
因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。
3 流量控制
发送方不能无脑的发数据给接收方,要考虑接收方处理能力。
如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。
为了解决这种现象发生,TCP 提供流量控制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量。
流量控制: 接收端通过ACK报文中的window字段告诉发送端自己还有多少可用缓冲区可以接收数据,发送端就根据window字段调整发送窗口大小,从而调整了发送的数据量,避免了接收端处理不过来的状况。
下面举个例子,为了简单起见,假设以下场景:
- 客户端是接收方,服务端是发送方
- 假设接收窗口和发送窗口相同,都为 200
- 假设两个设备在整个传输过程中都保持相同的窗口大小,不受外界影响

3.1 操作系统缓冲区与滑动窗口的关系
前面的流量控制例子,我们假定了发送窗口和接收窗口是不变的,但是实际上,发送窗口和接收窗口中所存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整。
当应用进程没办法及时读取缓冲区的内容时,也会对我们的缓冲区造成影响。
考虑以下场景:
- 客户端作为发送方,服务端作为接收方,发送窗口和接收窗口初始大小为 360;
- 服务端非常的繁忙,当收到客户端的数据时,应用层不能及时读取数据。

可见最后窗口都收缩为 0 了,也就是发生了窗口关闭。当发送方可用窗口变为 0 时,发送方实际上会定时发送窗口探测报文,以便知道接收方的窗口是否发生了改变。
当服务端系统资源非常紧张的时候,操作系统可能会直接减少了接收缓冲区大小,这时应用程序又无法及时读取缓存数据,那么这时候就有严重的事情发生了,会出现数据包丢失的现象。
Usable = SND.WND - (SND.NXT - SND.UNA) = 100 - (321 - 141) = -80
所以,如果发生了先减少缓存,再收缩窗口,就会出现丢包的现象。
RFC 793 和 RFC 1122 明确指出,接收方应该避免缩小窗口,尤其是当窗口右边界向左移动时。如果确实需要缩小窗口(比如应用层强制减少缓冲区),必须遵循以下原则:
- 先让发送方将窗口内的数据都发送并确认完毕(即等待窗口左边界右移),再通告新的、更小的窗口。
- 或者使用 零窗口通告(窗口为 0),让发送方停止发送,然后再逐步扩大窗口。
现代 TCP 实现(如 Linux)在收到 SO_RCVBUF 缩小时,并不会立即收缩通告窗口,而是等待应用程序读取数据,让窗口自然向右滑动,从而避免主动收缩。
3.2 窗口关闭
在前面我们都看到了,TCP 通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制。
如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。
3.2.1 窗口关闭潜在的危险
接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的。
那么,当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,那麻烦就大了。
这会导致发送方一直等待接收方的非 0 窗口通知,接收方也一直等待发送方的数据,如不不采取措施,这种相互等待的过程,会造成了死锁的现象。
TCP 是如何解决窗口关闭时,潜在的死锁现象呢?
为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。
如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
- 如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;
- 如果接收窗口不是 0,那么死锁的局面就可以被打破了。
窗口探查探测的次数一般为 3 次,每次次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。
3.3 糊涂窗口综合症
如果接收方太忙了,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。
到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症。
要知道,我们的 TCP + IP 头有 40 个字节,为了传输那几个字节的数据,要搭上这么大的开销,这太不经济了。
糊涂窗口综合症的现象是可以发生在发送方和接收方:
- 接收方可以通告一个小的窗口
- 而发送方可以发送小数据
于是,要解决糊涂窗口综合症,就解决上面两个问题就可以了
- 让接收方不通告小窗口给发送方
- 让发送方避免发送小数据
3.3.1 让接收方不通告小窗口
接收方通常的策略如下:
当「窗口大小」小于 min( MSS,缓存空间/2 ) ,也就是小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0,也就阻止了发送方再发数据过来。
等到接收方处理了一些数据后,窗口大小 >= MSS,或者接收方缓存空间有一半可以使用,就可以把窗口打开让发送方发送数据过来。
3.3.2 让发送方避免发送小数据
Nagle 算法是为了避免糊涂窗口综合征(Silly Window Syndrome, SWS)而设计的。它规定发送方在满足以下任一条件时,才允许发送一个小的 TCP 报文段(长度小于 MSS):
- 没有已发送但未确认的数据(即所有之前发送的数据都已收到 ACK)。此时发送方可以立即发送当前的小数据包。
- 待发送的数据量达到 MSS(最大报文段长度),或者达到发送窗口的一半(具体实现可能略有差异,但通常以 MSS 为界限)。此时即使还有未确认的数据,也可以发送一个“满”的报文段,以避免小包堆积。
- 紧急数据(URG 标志)或设置了 TCP_NODELAY 选项的情况下,可以绕过 Nagle 算法直接发送。
核心思想:当存在未确认的数据时,新产生的小数据包被暂时缓存,等待确认到达或数据累积到足够大再发送,从而避免大量微小报文段在网络中往返,防止接收方因通告小窗口而引发糊涂窗口综合征。
另外,Nagle 算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如,telnet 或 ssh 这样的交互性比较强的程序,则需要关闭 Nagle 算法。
可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭)setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int));
3.3.3 捎带应答


4 拥塞控制
流量控制:接收方处理能力有限的问题。如果发送方不管接收方能否接收,一直高速发送数据,接收方应用程序读取速度跟不上,数据就会堆积在接收缓存中,最终溢出,导致数据被丢弃。
拥塞控制: 网络中间设备(路由器)过载的问题。即使接收方有足够大的缓存,如果网络链路带宽有限或路由器队列已满,发送方过快发送的数据包仍会在网络中被丢弃,导致大量重传,网络效率急剧下降,甚至进入“拥塞崩溃”(congestion collapse)。
既然 TCP 有重传机制,丢包了重传就行,那为什么还要用流量控制限制发送方的速度?
核心答案:重传确实能保证可靠性,但效率极低。流量控制的目的是提前避免丢包,从而获得更高的吞吐量、更低的延迟和更少的带宽浪费。
| 问题 | 重传的代价 |
|---|---|
| 延迟增加 | 每次丢包都要等待超时或快速重传,数据传输延迟显著变大。必须等待超时或收到重复 ACK 才能感知丢包,然后重传。这至少需要 1 个 RTT(往返时间)。 |
| 带宽浪费 | 已经发送但被丢弃的数据占用了网络带宽,重传又占用一次,有效吞吐量减半甚至更低。 |
| 可能引发拥塞 | 大量重传会进一步加剧网络拥塞,导致更多丢包,进入恶性循环。 |
| 对发送方不公平 | 若多条 TCP 流共享同一链路,频繁重传的流会抢占更多资源,影响其他流的公平性。 |
总结:
重传是 TCP 保证可靠性的最后手段,而流量控制是预防性机制。
没有流量控制,接收方缓存溢出导致的丢包会反复触发重传,造成 带宽浪费、延迟飙升、吞吐量下降,甚至让网络进入低效状态。
流量控制通过端到端的反馈,让发送方自动适配接收方的处理能力,从而避免这些不必要的损耗。
为什么有了流量控制窗口,还需要拥塞窗口?
接收方的流量控制接口和发送方的拥塞窗口都是为了控制发送放的发送窗口[取两者的最小值],接收方的流量控制接口是为了避免接收方处理速度比发送放发送的慢,导致重复发送导致网络进一步更差;发送方的拥塞窗口是为了避免网络已经拥塞了,再继续快速发送导致网络进一步更差。两者的目的是相同的,只不过两者 是解决不同情况导致的网络更进一步的拥塞。
4.1 拥塞窗口
拥塞窗口 cwnd 是发送方维护的一个 的状态变量,它会根据网络的拥塞程度动态变化的。
我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。
拥塞窗口 cwnd 变化的规则:
- 只要网络中没有出现拥塞,cwnd 就会增大;
- 但网络中出现了拥塞,cwnd 就减少;
那么怎么知道当前网络是否出现了拥塞呢?
其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。
4.2 拥塞控制算法
拥塞控制主要是四个算法:
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
4.2.1 慢启动
TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?
慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。
这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,下面举个例子:
- 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据。
- 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个
- 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个
- 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个。

可以看出慢启动算法,发包的个数是指数性的增长。
那慢启动涨到什么时候是个头呢?
有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。
- 当 cwnd < ssthresh 时,使用慢启动算法。
- 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。
4.2.2 拥塞避免算法
前面说道,当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。
一般来说 ssthresh 的大小是 65535 字节。
那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。
接上前面的慢启动的例子,现假定 ssthresh 为 8:
- 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

4.2.3 拥塞发生算法
当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
超时重传快速重传
这两种使用的拥塞发送算法是不同的。
4.2.3.1 发生超时重传的拥塞发生算法
当发生了「超时重传」,则就会使用拥塞发生算法。
这个时候,sshresh 和 cwnd 的值会发生变化:
- ssthresh 设为 cwnd/2,
- cwnd 重置为 1

接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。
4.2.3.2 发生快速重传的拥塞发生算法
还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:
- cwnd = cwnd/2 ,也就是设置为原来的一半;
- ssthresh = cwnd;
- 进入快速恢复算法
4.2.4 快速恢复算法
快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。
正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:
- cwnd = cwnd/2 ,也就是设置为原来的一半;
- ssthresh = cwnd;
然后,进入快速恢复算法如下:
- 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了)
- 重传丢失的数据包
- 如果再收到重复的 ACK,那么 cwnd 增加 1
- 如果收到新数据的 ACK 后,设置 cwnd 为 ssthresh,接着就进入了拥塞避免算法

也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长。
5 显示拥塞通知
为什么tcp已经有了拥塞窗口,还需要ip显示拥塞?
简单来说,拥塞窗口(cwnd)是“刹车踏板”,而显式拥塞通知(ECN)是“仪表盘上的报警灯”。
在只有拥塞窗口的年代,TCP判断拥塞的方式非常原始且被动:只有等到数据包丢了,才知道网络堵了。这就像蒙着眼开车,只有撞到墙(丢包)才知道前面过不去了。
显式拥塞通知让路由器在队列即将满但还没满时,就在经过的数据包IP头(ECN字段)上做一个标记。
- 没有显式拥塞通知时:发送方只能等超时或收到重复ACK(确认信号,即发现丢包)→ 窗口减半 → 代价是丢包重传和超时等待。
- 有显式拥塞通知时:接收方收到被标记的数据包后,在ACK中告诉发送方“前面有点堵了”。发送方在还没丢包的情况下,就主动降低窗口 →→ 代价是轻微的减速。
这避免了数据包被路由器丢弃,从而避免了重传带来的额外延迟和带宽浪费。


ECN、ECT、CE区别: ECN 是一种网络拥塞控制机制,而不是 IP 头部的字段。而 ECT 和 CE 则是 IP 头部中的字段,用于支持 ECN 机制。
6 路径MTU发现




MTU: 由于以太网EthernetII最大的数据帧是1518Bytes这样,刨去以太网帧的帧头(DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和帧尾CRC校验部分4Bytes(这个部门有时候大家也把它叫做FCS),那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值我们就把它称之为MTU。这个就是网络层协议非常关心的地方,因为网络层协议比如IP协议会根据这个值来决定是否把上层传下来的数据进行分片。
MSS: Maximum Segment Size, 是TCP协议里面的一个概念。MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。
IP分片 UDP分片 tcp分段:
在网络通信中,IP 分片、UDP 分片和 TCP 分段是三种不同的数据包处理机制,它们有各自的作用和特点:IP 分片:
- IP 分片是指当一个 IP 数据包的大小超过了网络传输的最大传输单元(MTU)时,路由器或者主机会将>这个大的 IP 数据包分割成多个较小的数据包,以便能够在网络中传输。这些较小的数据包就是 IP 分片。
IP 分片是在网络层(OSI 模型的第三层)进行的操作,它仅仅涉及 IP 数据包的头部,不会深入到传输层或应用层协议的数据部分。UDP 分片:
- UDP 是用户数据报协议,它位于传输层(OSI 模型的第四层)。UDP 数据包通常由 IP 数据包承载,但 UDP 本身并不支持分段或分片。
UDP 数据包不会被路由器或主机在传输过程中进行分段或分片处理。如果 UDP 数据包的大小超过了网络传输的最大传输单元(MTU),则它将被拆分成 IP 分片。TCP 分段:
- TCP 是传输控制协议,也位于传输层。TCP 使用分段(Segmentation)而不是分片的概念。分段是指 TCP 在发送数据时,将较大的数据流分割成多个小的数据段进行传输。
TCP 分段是在传输层进行的操作,它不仅涉及 TCP 数据包的头部,还包括数据包的有效负载。TCP 在分段时会考虑到网络的 MTU,并尽量使得每个 TCP 分段的大小适合网络传输的 MTU。
总的来说,IP 分片是在 IP 层进行的操作,用于处理 IP 数据包大小超过 MTU 的情况;UDP 不支持分段或分片,而 TCP 则通过分段来进行数据传输,并且会根据网络的 MTU 动态调整分段的大小。
更多推荐
所有评论(0)