dw网站建设字体颜色,图文广告培训班多少钱,seo搜索引擎优化内容,app商城软件一、TCP 重传、滑动窗口、流量控制、拥塞控制
1.1、重传机制
在 TCP 中#xff0c;当发送端的数据达到接受主机时#xff0c;接收端主机会返回一个确认应答消息#xff0c;表示已收到消息。但是在复杂的网络中#xff0c;并一定能顺利正常的进行数据传输#xff0c;当发送端的数据达到接受主机时接收端主机会返回一个确认应答消息表示已收到消息。但是在复杂的网络中并一定能顺利正常的进行数据传输万一数据在传输过程中丢失了呢
所以 TCP 针对数据包丢失的情况会用重传机制解决
超时重传快速重传SACKD-SACK
1.1.1、超时重传
重传机制的其中一个方式就是在发送数据时设定一个定时器当超过指定的时间后没有收到对方的 ACK 确认应答报文就会重发该数据也就是我们常说的超时重传。
TCP 会在以下两种情况下发生超时重传
数据包丢失确认应答丢失
超时时间应该设置为多少呢
我们先了解什么是 RTTRound-Trip Time 往返时延从下图中就可以知道 RTT 指的是数据发送时刻到接收到确认的时刻的差值也就是包的往返时间。超时重传时间是以 RTORetransmission Timeout 超时重传时间表示。假设在重传的情况下超时时间 RTO【较长或较短】时会发生什么呢
当超时时间 RTO 较大时重发就很慢丢了很久才重传没有效率性能差当超时时间 RTO 较小时会导致可能并没有丢就重发于是重发的就快会增加网络拥塞导致更多的超时更多的超时导致更多的重发
综合上面的可以得知超时重传时间 RTO 的值应该略大于报文往返 RTT 的值
可能大家觉得超时重传时间 RTO 的计算并不复杂就是在发送端发包时记下 t0然后接收端再把这个 ack 回来时记一个 t1于是 RTT t1 - t0。但是没那么简单这只是一个采样不能代表普遍情况。实际上【报文往返 RTT 的值】是经常变化的因为我们的网络也是时常变化的。就因为【报文往返 RTT 的值】是经常波动变化的所以【超时重传时间 RTO 的值】应该是一个动态变化的值。
Linux 是如何计算 RTO 的呢
需要 TCP 通过采样 RTT 的时间然后进行加权平均算出一个平滑的 RTT 的值而且这个值还是要不断变化的因为网络状况不断地变化除了采样 RTT还要采样 RTT 的波动范围这样就避免如果 RTT 有一个大的波动的话很难被发现的情况
1.1.2、快速重传
TCP 还有一种快速重传Fast Retransmit机制它不以时间为驱动而是以数据驱动重传。 如上图发送出了 12345 份数据
第一份 Seq1 先送到了于是就 ACK 回 2结果 Seq2 因为某些原因没收到Seq3 到达了于是还是 ACK 回 2后面的 Seq4 和 Seq5 都到了但还是 ACK 回 2因为 Seq 还是没有收到发送端收到了三个 ACK 2 的确认知道了 Seq2 还没有到达就会在定时器过期之前重传丢失的 Seq2最后收到了 Seq2此时因为 Seq3Seq4Seq5 都收到了于是 ACK 回 6
由于 TCP 采用的是累计确认机制即当接收端收到比期望序号大的报文段时便会重复发送最近一次确认的报文段的确认信号称之为冗余 ACKduplicate ACK
所以快速重传机制的工作机制是当收到三个相同的 ACK 报文时会在定时器过期之前重传丢失的报文段。快速重传机制只解决了一个问题就是超时时间的问题但是它依然面临着另外一个问题就是重传的时候是重传一个还是重传所有的问题
举个假设发送端发了 6 个数据编号的顺序是 Seq1 ~ Seq6但是 Seq2、Seq3 都丢失了那么接收方在收到 Seq4Seq4Seq6 时都是回复 ACK2 给发送方但是发送方并不清楚这连续的 ACK2 是接收方收到哪个报文而回复的那是选择重传 Seq2 一个报文还是重传 Seq2 之后已发送的所有报文呢
如果只选择重传 Seq2 一个报文那么重传的效率很低因为对于丢失的 Seq3 报文还得在后续收到三个重复的 ACK3 才能触发重传。如果选择重传 Seq2 之后已发送的所有报文虽然能同时重传已丢失的 Seq2 和 Seq3 报文但是 Seq4Seq5Seq6 的报文是已经被接受过了对于重传 Seq4 ~ Seq6 这部分数据相当于做了一次无用功浪费资源
可以看到不管是重传一个报文还是重传已发送的报文都存在问题。所以就有了 SACK 方法。
1.1.3、SACK 方法
SACK 方法Selective Acknowledgment选择性确认
这种方式需要在 TCP 头部【选项】字段里加一个 SACK 的东西它可以将已收到的数据的信息发送给【发送方】这样发送方就可以知道哪些数据收到了哪些数据没收到知道了这些信息就可以只重传丢失的数据。 发送方收到了三次同样的 ACK 确认报文于是就会触发快速重传机制通过 SACK 信息发现只有 200~299 这段数据丢失则重发时就只选择这个 TCP 段进行重传。如果要支持 SACK 必须双方都要支持。在 Linux 下可以通过 net.ipv4.tcp_sack 参数打开这个功能。
1.1.4、Duplicate 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 包丢了而是因为网络延迟了
可见D_SACK 有这么几个好处
可以让【发送方】知道是发出去的包丢了还是接收方回应的 ACK 包丢了可以知道是不是【发送方】的数据包被网络延迟了可以知道网络中是不是把【发送方】的数据包给复制了
1.2、滑动窗口
TCP 是每发送一个数据都要进行一次确认应答。当上一个数据包受到了应答再发送下一个。这个模式就有点像面对面聊天你一句我一句这种方式的缺点就是效率极低。在 TCP 中就是数据包的往返时间长通信效率低
为了解决这个问题 TCP 引入了窗口这个概念。即使在往返时间较长的情况下它也不会降低网络通信的效率那么有了窗口就可以指定窗口的大小窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。
窗口的实现实际上是操作系统开辟的一个缓存空间发送主机在等到确认应答返回之前必须在缓冲区中保留已发送的数据。如果按期收到确认应答此时数据就可以从缓存区清楚。 图中的 ACK 600 确认应答报文丢失也没关系因为可以通过下一个确认应答进行确认只要发送方收到了 ACK 700 确认应答就意味着 700 之前的所有数据【接收方】都收到了。这个模式就叫确认应答或者累计应答。
此时窗口大小由哪一方决定呢
TCP 头里有一个字段叫 Window也就是窗口大小。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据而不会导致接收端处理不过来。所以通常窗口的大小是由接收方的窗口来决定的。发送方发送的数据大小不能超过接收方的窗口大小否则接收方就无法正常接收到数据。
发送方的滑动窗口
根据情况分为四个部分 #1 是确认并收到 ACK 确认的数据#2 是已发送但未收到 ACK 确认的数据#3 是未发送但总大小在接收方处理范围内接收方还有空间#4 是未发送但总大小超过接收方处理范围接收方没有空间
当发送方把数据【全部】都一下发送出去后可用窗口的大小就为 0 了表明可用窗口耗尽在没收到 ACK 确认之前是无法继续发送数据了 当收到之前发送的数据的 ACK 确认应答报文后如果发送窗口的大小没有变化则滑动窗口往右边移动相应的大小因为这个大小的数据被应答确认下来。接下来相应大小的字节数又变成了可用窗口那么后续也就可以发送空闲大小的剩余数据了。 程序是如何表示发送方的四个部分呢
TCP 滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针指特定的序列号一个是相对指针需要做偏移。 SND.WND表示发送窗口的大小大小是由接收方指定的SND.UNASend Unacknoleged是一个绝对指针它指向的是已发送但未收到确认的第一个字节的序列号也就是 #2 的第一个字节SND.NXT也是一个绝对指针它指向未发送但可发送范围的第一个字节的序列号也就是 #3 的第一个字节指向 #4 的第一个字节是个相对指针它需要 SND.UNA 指针加上 SND.WND 大小的偏移量就可以指向 #4 的第一个字节了
可用窗口大小 SND.WND - SND.NXT - SND.UNA
接收方的滑动窗口 #1 #2 是已成功接收并确认的数据等待应用进程读取#3 是未收到数据但可以接收的数据#4 未收到数据并不可以接收的数据
其中三个接收部分使用两个指针进行划分
RCV.WND表示接收窗口的大小它会通告给发送方RCV.NXT是一个指针它指向期望从发送方发送来的下一个数据字节的序列号也就是 #3 的第一个字节指向 #4 的第一个字节是个相对指针它需要 RCV.NXT 指针加上 RCV.WND 大小的偏移量就可以指向 #4 的第一个字节了
接收窗口和发送窗口的大小是相等的吗
并不是接收窗口的大小是约等于发送窗口的大小的。
因为滑动窗口并不是一成不变的。比如当接收方的应用进程读取数据的速度非常快的话这样的话接收窗口就可以很快的空缺出来。那么新的接收窗口的大小是通过 TCP 报文中的 Window 字段来告诉发送端那么这个传输过程是存在时延的所以接收窗口和发送窗口是约等于的关系。
1.3、流量控制
发送方不能无脑的发数据给接收方靠考虑接收方的处理能力
如果一直无脑的发数据给对方但对方处理不过来那么就会导致触发重传机制从而导致网络流量的无端的浪费。
为了解决这一问题TCP 提供了一种机制可以让【发送方】根据【接收方】的实际接受能力控制发送的数据量这就是所谓的流量控制。
举个
客户端是接收方服务端是发送方假设接收窗口和发送窗口相同都为 200假设两个设备在整个传输过程中都保持相同的窗口大小不受外界影响
过程如下 说明一下以下过程
客户端向服务端发送请求数据报文。这里要说明下本次是把服务端作为发送方所以没有画出服务端的接收窗口服务端收到请求报文后发送确认报文和 80 字节的数据于是可用窗口 Usable 减少为 120 字节同时 SND.NXT 指针也向右偏移 80 字节后指向 321 这不意味着下次发送数据的时候序列号是 321客户端在收到 80 字节数据后于是接收窗口往右偏移 80 字节RCV.NXT 也就指向 321这意味着客户端期望的下一个报文的序列号是 321接着发送确认报文给服务端服务端再次发送了 120 字节数据于是可用窗口耗尽为 0 服务端无法再继续发送数据客户端收到 120 字节的数据后于是接收窗口往右移动 120 字节RCV.NXT 也就指向 441 接着发送确认报文给服务端服务端收到对 80 字节的确认报文后SND.UNA 指针往右偏移后指向 321于是可用窗口 Usable 增大到 80服务端收到对 120 字节的确认报文后SND.UNA 指针往右偏移后指向 441于是可用窗口 Usable 增大到 200服务端可以继续发送了于是发送了 160 字节的数据后SND.NXT 指向 601于是可用窗口 Usable 减少到 40客户端收到 160 字节后接收窗口往右移动了 160 字节RCV.NXT 也就是指向了 601接着发送确认报文给服务端服务端收到对 160 字节数据的确认报文后 发送窗口往右移动了 160 字节于是 SND.UMA 指针偏移了 160 后指向 601可用窗口 Usable 也就增大到了 200
1.3.1、操作系统缓冲区与滑动窗口的关系
上述的流量控制例子我们假定了发送窗口和接收窗口是不变的但是实际上发送窗口和接收窗口中所存放的字节数都是放在操作系统内核缓冲区中的而操作系统的缓冲区会被操作系统调整。
当应用进程无法即使读取缓冲区的内容时也会对我们的缓冲区造成影响
那么操作系统的缓冲区是如何影响发送窗口和接收窗口的呢
直接来个
当应用程序没有及时读取缓存时发送窗口和接收窗口的变化
考虑一下场景
客户端做为发送方服务端作为接收方发送窗口和接收窗口初始大小为 360服务端非常的繁忙当收到客户端的数据时应用层不能及时读取数据 过程说明
客户端发送 140 字节数据后可用窗口变为 220360-140服务端收到 140 字节数据但是服务端非常繁忙应用进程只读取了 40 个字节还有 100 字节占用着缓冲区于是接收窗口收缩到了 260360-100最后发送确认信息时将窗口大小通告给客户端客户端收到确认和窗口通告报文后发送窗口减少为 260客户端发送 180 字节数据此时可用窗口减少到 80 服务端收到 180 字节数据 但是应用程序没有读取任何数据这 180 字节直接就留在了缓冲区于是接收窗口收缩到了 80260-180并在发送确认信息时通过窗口大小给客户端客户端收到确认和窗口通告报文后发送窗口减少为 80客户端发送 80 字节数据后可用窗口耗尽服务端收到 80 字节数据但是应用进程依然没有读取任何数据这 80 字节留在了缓冲区于是接收窗口收缩到了 0并在发送确认信息时通过窗口大小给客户端客户端收到确认和窗口通告后发送窗口减少为 0
可见最后窗口都收缩为 0 了也就是发生了窗口关闭。当发送方可用窗口为 0 时发送方实际上会定时发送窗口探测报文以便知道接收方的窗口是否发生了改变。
二号
当服务端系统资源非常紧张的时候操作系统可能会直接减少了接收缓冲区大小这时应用程序又无法即使读取缓存数据那么这时候就有严重的事情发生了会出现数据包丢失的情况。 具体过程
客户端发送 140 字节的数据于是可用窗口减少到了 220服务端因为现在非常的繁忙操作系统于是就把接收缓存减少了 120 字节当收到 140 字节数据后又因为应用程序没有读取任何数据所以 140 字节留在了缓冲区中于是接收窗口大小从 360 收缩成了 100最后发送确认信息时通告窗口大小给对方此时客户端因为还没有收到服务端的通告窗口报文所以不知道此时接收窗口收缩成了 100客户端只会看到自己的可用窗口还有 220所以客户端就发送了 180 字节数据于是可用窗口减少到 40服务端收到了 180 字节数据时发现数据大小超过了接收窗口的大小于是就把数据包丢失了客户端收到第 2 步时服务端发送的确认报文和通告窗口报文尝试减少发送窗口到 100把窗口的右端向左收缩了 80此时可用窗口的大小就会出现诡异的负值
所以如果发生了先减少缓存再收缩窗口就会出现丢包的现象
为了防止这种情况的发生TCP 规定是不允许同时减少缓存又收缩窗口的而是采用先收缩窗口过段时间再减少缓存这样就可以避免这种情况
1.3.2、窗口关闭
如上述TCP 通过让接收方指明希望从发送接收的数据大小窗口大小来进行流量控制。
如果窗口大小为 0 时就会阻止发送方给接收方传递数据直到窗口变为非 0 为止这就是窗口关闭
窗口关闭潜在的危险
接收方向发送通告窗口大小时是通过 ACK 报文来通告的。
那么当发生窗口关闭时接收方处理完数据后会向发送方通告一个窗口非 0 的 ACK 报文如果这个通告窗口的 ACK 报文在网络中丢失了那麻烦就大了。 这会导致发送方一直等待接收方的非 0 窗口通知接收方也一直等待发送方的数据如不采取措施这种相互等待的过程会造成了死锁的现象。
TCP 是如何解决窗口关闭时潜在的死锁现象呢
为了解决这个问题TCP 为每个连接设有一个持续定时器只要 TCP 连接一方收到对方的零窗口通知就启动持续计时器。
如果持续计时器超时就会发送窗口探测报文而对方在确认这个探测报文时给出自己现在的接收窗口大小。 如果接收窗口依然为 0 那么收到这个报文的一方就会重新启动持续计时器
如果接收窗口不是 0 那么死锁的局面就可以被打破了
窗口探测的次数一般是 3 次每次大约 30-60s不同的实现方式可能会不一样。如果三次过后接收窗口还是 0 的话有的 TCP 实现就会发 RST 报文来中断连接
1.3.3、糊涂窗口综合症
如果接受方太忙了来不及取走接收窗口里的数据那么就会导致发送方的发送窗口越来越小。到最后如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口而发送方会义无反顾地发送这几个字节这就是糊涂窗口综合症。
要知道我们的 TCP IP 头部有 40 个字节为了传输那几个字节的数据要搭上这么大的开销这太不经济了。就好比一个可以承载 50 人的大巴车每次来了一两个人就直接发车。除非家里有矿的大巴车司机才敢这么玩不然迟早破产。解决这个问题也不难大巴车司机等乘客数量达到 25 个才认定发车。
直接举个
接收方的窗口大小为 360 字节但接收方由于某些原因陷入困境假设接收方的应用层数据读取的能力如下
接收方每接收 3 个字节应用程序就只能缓存从缓冲区中读取 1 个字节的数据在下一个发送方的 TCP 段到达之前应用程序还从缓冲区中读取了 40 个额外的字节 由上图可知窗口大小不断减少了并且发送的数据都是比较小的
所以糊涂窗口综合症的现象是可以发生在发送方和接收方
接收方可以通告一个小的窗口而发送方可以发送小数据
于是要解决糊涂窗口综合症就要同时解决上面两个问题就行
让接收方不通告小窗口给发送方让发送方避免发送小数据
怎么让接收方不通告小窗口呢
接收方通常的策略如下
当【窗口大小】小于 minMSS缓存空间 / 2也就是小于 MSS 与 1/2 缓存大小中的最小值时就会向发送方通告窗口为 0 也就阻止了发送方再发数据过来
等到接收方处理了一些数据后窗口大小 MSS 或者接收方缓存空间有一半可以使用就可以把窗口打开让发送方发送数据过来。
怎么让发送方避免发送小数据呢
使用 Nagle 算法该算法的思路是延时处理只有满足下面两个条件中的任意一个条件才可以发送数据
要等到窗口大小 MSS并且数据大小 MSS收到之前发送数据的 ACK 回包
只要上面两个条件都不满足发送方一直在囤积数据直到满足上面的发送条件
注意如果接受方不能满足【不通告小窗口给发送方】那么即使开了 Nagle 算法也无法避免糊涂窗口综合症因为如果对端 ACK 回复很快的话Nagle 算法就不会拼接太多的数据包这种情况下依然会有小数据包的传输网络总体利用率依然很低。
所以接收方得满足【不通告小窗口给发送方】 发送方开启 Nagle 算法才能避免糊涂窗口综合症
另外Nagle 算法默认是开启的如果对于一些需要小数据包交互的场景的程序比如telnet 或者 ssh 这样交互性比较强的程序则需要关闭 Nagle 算法
可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法关闭 Nagle 算法没有全局参数需要根据每个应用自己的特点来关闭
setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char* )value, sizeof(int));
1.4、拥塞控制
有的小伙伴可能会问为什么要有拥塞控制啊不是有流量控制了吗
前面的流量控制是避免【发送方】的数据填满【接收方】的缓存但是并不知道网络中发生了什么。一般来说计算机网络都处在同一个共享环境因此也有可能会因为其他主机之间的通信使得网络拥堵。
在网络发送拥堵时如果继续发送大量数据包可能会导致数据包时延、丢失等这时 TCP 就会重传数据但是一重传就会导致网络的负担更重于是会导致更大的延迟以及更多的丢包这个情况就会进入恶性循环被不断放大。
所以TCP 不能忽略网络上发生的事它被设置成一个无私的协议当网络发送拥堵时TCP 会自我牺牲降低发送的数据量。于是就有了拥塞控制控制的目的就是避免【发送方】的数据填满整个网络。为了在【发送方】调节所要发送的数据的量定义了一个叫做【拥塞窗口】的概念。
什么是拥塞窗口和发送窗口有什么关系呢
拥塞窗口 cwnd 是发送方维护的一个状态变量它会根据网络的拥塞程度动态变化的。
我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系那么由于加入了拥塞窗口的概念后此时发送的窗口的值是 swnd mincwndrwnd也就是拥塞窗口和接收窗口中的最小值
拥塞窗口 cwnd 变化的规则
只要网络中没有出现拥塞 cwnd 就会增大但网络中出现了拥塞cwnd 就减少
那么怎么知道当前网络中是否出现了拥塞呢
其实只要【发送方】没有在规定时间内接收到 ACK 应答报文也就是发生了超时重传就会认为网络出现了拥塞。
拥塞控制有哪些控制算法呢
慢启动拥塞避免拥塞发生快速恢复
1.4.1、慢启动
TCP 在刚建立连接完成后首先是有个慢启动的过程这个慢启动的意思就是一点一点的提高发送数据包的数量如果一上来就发大量的数据这不是给网络添堵吗
慢启动算法的规则当发送方每收到一个 ACK 拥塞窗口 cwnd 的大小就会加 1。
这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等下面举个
连接建立完成后一开始初始化 cwnd 1表示可以传一个 MSS 大小的数据
当收到一个 ACK 确认应答后cwnd 增加 1于是一次能够发送 2 个
当收到 2 个的 ACK 确认应答后cwnd 增加 2于是就可以比之前多发 2 个所以这一次能够发送 4 个
当这 4 个的 ACK 确认到来时每个确认 cwnd 增加 14 个确认 cwnd 增加 4于是就可以比之前多发 4 个所以这一次能够发送 8 个 可以看到慢启动算法发包的个数是指数性的增长。那么什么时候慢启动涨到个头呢
有一个叫慢启动门限 ssthreshslow start threshold状态变量
当 cwnd ssthresh 时使用慢启动算法当 cwnd ssthresh 时就会使用【拥塞避免算法】
1.4.2、拥塞避免算法
前面说到当拥塞窗口 cwnd 【超过】慢启动门限 ssthreshold 就会进入拥塞避免算法。
一般来说 ssthresh 的大小是 65535 字节。那么进入拥塞避免算法后它的规则是每当收到一个 ACK 时cwnd 增加 1/cwnd 。接上前面的慢启动的现规定 ssthresh 为 8
当 8 个 ACK 应答确认到来时每个确认增加 1/8 8 个 ACK 确认 cwnd 一共增加 1于是这一次能够发送 9 个 MSS 大小的数据变成了线性增长。
过程如下 所以我们可以发现拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长还是增长阶段但是增长速度缓慢了一点。
就这么一直增长着后网络就会慢慢进入了拥塞的状态于是就会出现丢包现象这时就需要对丢失的数据包进行重传。当触发了重传机制也就进入了【拥塞避免算法】
1.4.3、拥塞发生
当网络出现拥塞也就是会发生数据包重传重传机制主要有两种
超时重传快速重传
这两种使用的拥塞算法是不同的接下来分别说说
发生【超时重传】则会使用【拥塞发生算法】
ssthresh 设为 cwnd / 2cwnd 重置为 1 是恢复为 cwnd 初始化值这里假定为 cwnd 的初始化值为 1
如何查看 系统的 cwnd 初始化值
Linux 针对每一个 TCP 连接的 cwnd 初始化值是 10也就是 10 个 MSS我们可以用 ss-nli 命令查看每一个 TCP 连接的 cwnd 初始化值如图 拥塞发生算法的变化如图 接着就重新开始慢启动慢性启动是会突然减少数据流的这真是一旦【超时重传】马上回到解放前。但是这种方式太激进反应也很强烈会造成网络卡顿。
发生【快速重传】则也会使用【拥塞发生算法】不过有些许不同
还有个更好的办法前面我们说过【快速重传算法】。当接收方发现丢了一个中间包的时候发送三次前一个包的 ACK于是发送端就会快速地重传不必等待超时再重传。
TCP 认为这种情况不严重因为大部分没丢只丢了一小部分则 ssthresh 和 cwnd 变化如下
cwnd cwnd / 2也就是设置为原来的一半ssthresh cwnd进入快速恢复算法
1.4.4、快速恢复
快速重传和快速恢复算法一般同时使用快速恢复算法是认为你还能收到 3 个重复 ACK 说明网络也不那么糟糕所以没必要像 RTO 超时那么强烈
正如前面所说进入快速恢复之前cwnd 和 ssthresh 已被更新
cwnd cwnd / 2也就是设置为原来的一半ssthresh cwnd
然后进入快速恢复算法如下
拥塞窗口 cwnd ssthresh 33 的意思是确认有 3 个数据包被收到了重传丢失的数据包如果再收到重复的 ACK 那么 cwnd 增加 1如果收到新数据的 ACK 后把 cwnd 设置为第一步中的 ssthresh 的值原因是该 ACK 确认了新的数据说明从 dulicated ACK 时的数据都已经收到改恢复过程已经结束了可以回到恢复之前的状态了也即可以再次进入拥塞避免算法
快速回复算法变化过程如图 Tip在快速恢复算法过程中为什么收到新的数据后cwnd 设置回了 ssthresh
在快速恢复的过程中首先 ssthresh cwnd / 2然后 cwnd ssthresh 3 表示网络可能出现了阻塞所以需要减小 cwnd 来避免加 3 代表快速重传时已经确认收到了 3 个重复的数据包随后继续重传丢失的数据包如果再收到重复的 ACK 那么 cwnd 增加 1 。加 1 代表每个收到的重复的 ACK 包都已经离开了网络。这个过程的目的是尽快将丢失的数据包发送给目标。如果收到新数据的 ACK 后把 cwnd 设置为第一部中的 sshtresh 的值恢复过程结束
首先快速恢复是拥塞发生后慢启动的优化其首要目的仍然是降低 cwnd 来减缓拥塞所以必然会出现 cwnd 从大到小的改变。
其次过程 2cwnd 逐渐加 1的存在是为了尽快将丢失的数据包发给目标从而解决拥塞的根本问题三次相同的 ACK 导致的快速重传所以这一过程中 cwnd 反而是逐渐增大的