TCP头部的报文结构
序号:seq 序号,占 32 位,用来标识从 TCP 源端向目的端发送的字节流,发起方发送数据时对此进行标记。
确认号:ack 序号,占 32 位,只有 ACK 标志位为1时,确认序号字段才有效,ack = seq + 1。
序号是本 TCP 报文数据部分的首字节序号,确认号是成功接收别人的 TCP 报文,并期待接收的下一个
TCP 报文中数据部分的首字节的序号。
序号主要解决数据包乱序的问题。
确认号解决丢包的问题。
三次握手
TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议,在发送数据前,通信双方必须在彼此间建立一条连接。
TCP 可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在 TCP 头部。当一个连接被建立或被终止时,交换的报文段只包含 TCP 头部,而没有数据。
三次握手:
-
刚开始客户端和服务端处于 CLOSED 状态,然后先是服务端主动监听某个端口,此时服务端处于 LISTEN 状态。
-
第一次握手:客户端随机初始化一个序列号 ISN,并向服务端发送一个 SYN 报文,此时客户端处于 SYN_SENT 状态。
-
第二次握手:服务器收到客户端的 SYN 报文之后,也会随机初始化自己的序列号,发给客户端一个 SYN + ACK 报文,ack 的值是客户端的 ISN + 1,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_RCVD 的状态。
-
第三次握手:客户端收到服务端的报文之后,会发送给服务端一个 ACK 报文,ack 的值是服务端的 ISN + 1,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISED 状态。服务端收到客户端的 ACK 报文之后,也处于 ESTABLISED 状态,此时,双方已建立起了连接。
第一次握手和第二次握手不能携带数据,第三次握手可以携带数据。
假如第一次握手可以携带数据,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,会让服务器花费很多时间、内存空间来接收这些报文。即第一次握手如果可以放数据的话,可能会让服务器更加容易受到攻击。
而对于第三次握手,此时客户端已经处于 established 状态,对于客户端来说已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据。
三次握手异常情况
TCP 第一次握手 SYN 丢包
当客户端发起的 TCP 第一次握手 SYN 包,在超时时间内没收到服务端的 ACK,就会在超时重传 SYN 数据包,每次超时重传的 RTO 是翻倍上涨的,直到 SYN 包的重传次数到达 tcp_syn_retries 值后,客户端不再发送 SYN 包。
TCP 第二次握手 SYN + ACK 丢包
从客户端的角度看,因为没有收到服务端的响应,误认为自己发出的 SYN 报文丢失,所以客户端 SYN 包会发生超时重传。
从服务端的角度看 ,因为没有收到客户端的 ACK 报文,服务端也会触发超时重传机制,重传 SYN + ACK 报文
TCP 第三次握手 ACK 丢包
如果第三次握手的 ACK 服务端无法收到,则服务端就会短暂处于 SYN_RCVD 状态,而客户端会处于 ESTABLISHED 状态。由于服务端一直收不到 TCP 第三次握手的 ACK,则会一直重传 SYN、ACK 包,直到重传次数超过 tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。
而客户端则会有两种情况:
-
如果客户端没发送数据包,一直处于
ESTABLISHED状态,然后经过较长一段时间才可以发现一个「死亡」连接,于是客户端连接就会断开连接。 -
如果客户端发送了数据包,一直没有收到服务端对该数据包的确认报文,则会一直重传该数据包,直到重传次数超过
tcp_retries2值(默认值 15 次)后,客户端就会断开 TCP 连接。
三次握手常见面试题
三次握手有什么作用?
-
确认双方的接收能力、发送能力是否正常。
-
同步双方的初始序列号。
-
避免资源浪费。
-
如果是 https 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成。
半连接队列和全连接队列
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接, 服务器会把此种状态下请求放在半连接队列(SYN 队列) 。
如果已经完成三次握手,服务端会把连接从半连接队列中移除,创建一个新的连接并放在全连接队列(Accept 队列) 中,等待进程调用 accept() 把连接取出来。
如果全连接队列溢出了,后续的 TCP 连接请求就会被丢弃。
SYN 攻击
攻击者伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入 SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。
解决方法:
-
增大 TCP 半连接队列。
-
开启 syncookies 功能:可以在不使用半连接队列的情况下成功建立连接。
-
减少 SYN + ACK 重传次数。
为什么是三次握手?两次不行吗?
三次握手才能保证双方具有接收和发送的能力。
只有三次握手才可以:
-
阻止重复历史连接的初始化。
-
同步双方的初始序列号。
-
避免资源浪费。
如果用了两次握手,则有可能会发生下面这种情况:
客户端向服务端发出一个连接请求,但由于某种原因该连接请求报文丢失了,于是客户端又向服务端发送了一个连接请求,这次一切正常,建立连接后进行数据传输,然后释放连接。注意:客户端一共发送了两个连接请求报文段,一个丢失,一个到达服务端。 但如果丢失的那个请求在某个网络节点滞留了,延迟了一会才到达服务端,这时服务端就会误以为客户端又发送了一次新的连接请求,而由于只有两次握手,此时服务端只要发出确认就能够建立新的连接了。但此时客户端会忽略服务端发来的确认,不进行数据传输,那么服务端就会一直等待客户端发送数据,造成资源的浪费。
四次挥手
- 第一次挥手:客户端发送一个 FIN,用来关闭客户端和服务端的数据传送,客户端进入 FIN_WAIT_1 状态。
- 第二次挥手:服务端收到 FIN 后,发送一个 ACK 给客户端,确认序号 ack = seq + 1,服务端进入 ****CLOSE_WAIT 状态。
- 第三次挥手:服务端发送一个 FIN,用来关闭服务端和客户端的数据传送,服务端进入 LAST_ACK 状态。
- 第四次挥手:客户端收到 FIN 后,发送一个 ACK 给服务端,客户端进入 TIME_WAIT 状态,服务端收到 ACK 后进入 CLOSED 状态,客户端在等待 2MSL 后进入 CLOSED 状态,完成四次挥手。
注意客户端发出确认报文后不是立马释放 TCP 连接,而是要经过 2MSL(最长报文段寿命的2倍时长)后才释放 TCP 连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束 TCP 连接的时间要比客户端早一些。
四次挥手常见面试题
为什么挥手需要四次?
-
关闭连接时,客户端向服务端发送
FIN,即第一次挥手时,仅仅表示客户端不再发送数据了但是还能接收数据 -
服务端收到客户端的
FIN报文时,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN报文给客户端来表示同意现在关闭连接。
所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次
第一次挥手丢失
客户端发给服务端的 FIN 报文丢失后,由于收不到服务端的 ACK,客户端会触发超时重传机制,重传 FIN 报文,当重传次数超过 tcp_orphan_retries 值时,不再发送 FIN 报文,再等待一段时间(一般为上次超时时间的2倍)后直接断开连接。
第二次挥手丢失
服务端给客户端发送的 ACK 报文丢失后, 由于 ACK 报文不会重传,所以客户端由于收不到服务端的 ACK 而触发超时重传机制重传 FIN 报文,直到收到服务端的 ACK 报文或达到最大重传次数。
第三次挥手丢失
服务端发送给客户端的 FIN 报文丢失后,由于收不到客户端的 ACK 报文,服务端会触发超时重传机制,重传 FIN 报文,当达到重传最大次数后,等待一段时间断开连接。
第四次挥手丢失
当客户端发送给服务端的 ACK 报文后,客户端就会进入 TIME_WAIT 状态,在此状态下会等待 2MSL 后断开连接。
当 ACK 报文丢失后,此时服务端会认为自己的 FIN 报文丢失,服务端超时重传 FIN 报文,客户端收到重传的 FIN 报文后会重置 2MSL 定时器,当服务端达到最大重传次数后停止重传,客户端等待 2MSL 后断开连接。
为什么 TIME_WAIT 需要等待 2MSL?
等待 2MSL 的原因主要有两个:
-
确保客户端最后的 ACK 报文能被服务端接收。
-
防止连接关闭后建立的新连接接收到旧连接的数据包。
确保客户端最后的 ACK 报文能让服务端接收
-
如果服务端发送了 FIN 后,没有接收到客户端的 ACK 报文,则会超时重传再次发送 FIN。
-
所以需要客户端等待 2MSL,确保服务端接收到了ACK报文。
防止连接关闭后建立的新连接接收到旧连接的数据包
假设 TIME_WAIT 没有等待时间或等待时间过短,而在上一个连接中有延迟的数据包,当这个端口建立新连接后,这个旧的数据包刚刚到达,此时会发生数据错乱。这样新的连接就会处理旧的服务端数据包,产生数据错乱等严重问题。
经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
服务器出现大量 TIME_WAIT 的原因?
TIME_WAIT 是主动关闭连接的一方才会出现的状态,如果服务器出现大量 TIME_WAIT,说明服务器主动断开了很多 TCP 连接。
HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
服务端主动断开连接的原因有三种:
-
HTTP 没有使用长连接:HTTP 没有使用长连接,就意味着服务器主动关闭时,每个连接都要进行四次挥手,而服务器端口、连接资源那么多,就会造成大量 TIME_WAIT 状态出现。
-
HTTP 长连接超时:可以往网络问题的方向排查,比如是否是因为网络问题,导致客户端发送的数据一直没有被服务端接收到,以至于 HTTP 长连接超时。
-
HTTP 长连接的请求数量达到上限:Web 服务端通常会有个参数,来定义一条 HTTP 长连接上最大能处理的请求数量,当超过最大限制时,就会主动关闭连接。
服务器出现大量 CLOSE_WAIT 状态的原因?
CLOSE_WAIT 状态是被动关闭方才会有的状态,而且如果被动关闭方没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。
当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。
如果已经建立连接,客户端故障了怎么办?
定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
如果已经建立连接,服务端故障了怎么办?
当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。
TCP 是如何保证可靠传输的?
序号:应用数据会被分割成数据块。TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
校验和:TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,在发送方计算,在接收方校验。目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
三次握手 & 四次挥手:TCP 通过三次握手建立连接和四次挥手关闭连接,确保通信的可靠性和数据的可靠传输。
流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 利用滑动窗口实现流量控制。
拥塞控制:TCP 使用拥塞控制算法来确保网络中没有过多的数据导致拥塞。当网络拥塞时,发送端会减少发送速率,以避免进一步加重网络拥塞。常用的拥塞控制算法包括慢启动、拥塞避免和快速重传等。
ARQ 协议:也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
超时重传:当 TCP 发出一个报文段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
TCP 中的粘包问题
TCP 传输数据的方式:TCP 在传输数据时,会给每个分割后的报文段分配一个序号,接收方在收到数据后,会按照序号排好,然后将其放置在 TCP 缓冲区中。同时 TCP 为了提升传输速度,若连续几次发送的数据都很少,TCP 会根据优化算法把多个数据合并成一个包发出。
粘包问题:多个数据包在一起,无法确定每个数据包之间的分割边界,从应用层角度看就像“粘”在一起。
发送方导致的数据粘包:
-
TCP 为了优化传输速度,收集到足够多的数据后才发送一包数据,因此发送方传输的数据出现了粘包问题。
-
当需要发送的数据大于 MSS 规定,那么 TCP 就会对数据包进行拆包,一个数据包会被分开传输,最终导致数据出现沾包问题。
接收方导致的数据粘包:
TCP 中,如果数据被接收后,应用程序没有及时读取缓冲区中的数据报文,就会导致缓冲区中堆积大量的报文数据。应用程序无法知道每个数据包之间的分割边界,站在应用层的角度来看,所有的数据包就好像都“沾”在一起了一样,应用程序根据预先设定好的大小从缓冲区中接收数据,最终会一次性读取到多包数据。
粘包问题解决方案:
-
当使用 TCP 短连接时,不必考虑粘包问题。
-
当发送无结构数据,如文件传输时,也不需要考虑粘包问题,因为这类数据只管发送和接收保存即可。
-
如果使用长连接,那么则需要考虑沾包问题:
-
如果发送的报文都是相同的结构,那么可以在首部中添加数据长度,接收方根据首部中记录的数据大小读取数据。
-
将每个数据包封装成固定长度,不够的用0补齐,接收方每次按照固定大小读取数据即可。
-
在数据之间设置边界,比如添加特殊符号,这样接收方收到数据时,根据特殊符号分割数据即可。
-
重传机制
TCP 针对数据包丢失的情况会使用重传机制解决。
常见的重传机制:
-
超时重传
-
快速重传
-
SACK
-
D-SACk
超时重传
超时重传即:在发送数据时,设定一个定时器,当超过定时器设定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据。
发生超时重传的两种情况:
-
数据包丢失
-
确认应答丢失
超时重传时间应该设为多少?
首先我们要知道一个概念:RTT(Round-Trip Time 往返时延),从下图我们就可以知道:
- 超时重传时间过长时,会使网络的空隙时间增大,降低网络传输效率。
- 超时重传时间过短时,可能在发生超时重传后才收到 ACK 报文,造成不必要的重传,导致网络负荷增大。
所以超时重传时间应设置为略大于 RTT 的值。如果超时重发的数据,再次超时,又需要重传的时候,TCP 的策略是超时的间隔加倍。
也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。
快速重传
TCP 还有另外一种快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。
快速重传机制:
在上图,发送方发出了 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 。
补充:SeqNum 和 ACK 都是以字节数为单位的,也就是说假设你收到了1、2、4 但是 3 没有收到你不能 ACK 5,如果你回了5那么发送方就以为你5之前的都收到了。
所以只能回复确认最大连续收到包,也就是 3。(ack = seq + 1)
所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。
比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。
根据 TCP 不同的实现,以上两种情况都是有可能的。可见,这是一把双刃剑。
为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。
SACK 方法
还有一种实现重传机制的方式叫:SACK(Selective Acknowledgment 选择性确认)。
普通 ACK 只能确认接收到的最后一个连续数据,而 SACK 允许接收端告知发送端哪些具体的数据段已经成功接收,哪些数据段丢失,减少不必要的重传。
如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重传机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。
D-SACK
D-SACK(Duplicate Selective Acknowledgment)是 SACK 的扩展,通过 D-SACK 可以使发送方确定有哪些数据被重复接收,从而识别和处理不必要的重传。D-SACK 通过在 SACK 块中包含重复数据段的信息来实现。
工作原理:
-
当接收端检测到重复数据段时,会在发送的 SACK 中包含这些重复数据段的起始和结束序列号。
-
发送端接收到带有 D-SACK 信息的 ACK 后,可以识别出哪些数据包是重复的,从而调整其重传策略。例如,发送端可以减少拥塞窗口的减少,避免误判网络拥塞。
流量控制
滑动窗口
流量控制是为了控制发送方发送速率,保证接收方来得及接收。使用滑动窗口进行流量控制。
窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。
接收方发送的确认报文中窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据
窗口关闭
接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的。
当发送窗口关闭时,在接受方处理完数据后,接受方会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,那就会造成很严重的后果。
如何解决这个潜在的死锁现象?
为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。
如果持续计时器超时,就会发送窗口探测 ( Window Probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
-
如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;
-
如果接收窗口不是 0,那么死锁的局面就可以被打破了。
窗口探查探测的次数一般为 3 次,每次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。
糊涂窗口综合症
当接收方来不及取走窗口里的数据时,就会导致发送方的发送窗口越来越小。最后可能会导致发送方一次只发送几个字节的数据,得不偿失,这就是糊涂窗口综合症。
糊涂窗口综合症的解决方法:
-
接收方不通告小窗口给发送方。
-
发送方避免发送小数据。
接收方不通告小窗口给发送方
当窗口长度小于 MSS(最大报文段大小) 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0,也就阻止了发送方再发数据过来。
发送方避免发送小数据
使用 Nagle 算法,该算法的思路是延时处理,只有满足下面两个条件中的任意一个条件,才可以发送数据:
-
要等到窗口大小 >= MSS 并且 数据大小 >= MSS。
-
收到之前发送数据的 ack 回包。
注意,如果接收方不能满足不通告小窗口给发送方,那么即使开了 Nagle 算法,也无法避免糊涂窗口综合症,因为如果对端 ACK 回复很快的话(达到 Nagle 算法的条件二),Nagle 算法就不会拼接太多的数据包,这种情况下依然会有小数据包的传输,网络总体的利用率依然很低。
所以,接收方得满足不通告小窗口给发送方+ 发送方开启 Nagle 算法,才能避免糊涂窗口综合症。
拥塞控制
拥塞控制是为了避免发送方的数据填满整个网络。
为了在发送方调节所要发送数据的量,定义了一个叫做拥塞窗口的概念。
拥塞窗口 cwnd是发送方维护的一个状态变量,它会根据网络的拥塞程度动态变化。cwnd指目前自己还能传输的数据量大小。
拥塞窗口 cwnd 变化的规则:
-
只要网络中没有出现拥塞,
cwnd就会增大; -
一旦网络中出现了拥塞,
cwnd就减少。
如何确定网络是否出现拥塞?
只要发送方没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。
对于拥塞控制来说,TCP 每条连接都需要维护两个核心状态:
-
拥塞窗口 cwnd
-
慢启动阈值 ssthresh
拥塞控制主要有四个算法:
-
慢启动
-
拥塞避免
-
快重传
-
快恢复
慢启动与拥塞避免
拥塞窗口(cwnd)从1开始,在没有达到慢启动阈值 ssthresh 前指数增长,达到 ssthresh 后线性增长。一般来说,ssthresh 的值为65535字节。
当 cwnd < ssthresh 时,使用慢启动算法。
慢启动算法:当发送方每收到一个 ACK,就拥塞窗口 cwnd 的大小就会加 1。
当 cwnd >= ssthresh 时,就会使用拥塞避免算法。
拥塞避免:每当收到一个 ACK 时,cwnd 增加 1/cwnd。
一旦发生拥塞,ssthresh = cwnd / 2,并且 cwnd 大小设置为1,重新慢启动。
快重传
上面提到当拥塞发生时,我们将 ssthresh 设置为 cwnd / 2,且 cwnd 重置为 1,然后重新开始慢启动,数据流突然被减少,所以这种方式过于激进,会造成网络卡顿。
快重传就是为了使发送方尽快进行重传,而不是等待超时重传,因为当超时重传的时候,就会认为发生了拥塞。
快重传:当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,发送方收到连续三个重复的 ACK 后就会立即重传,不必等待超时再重传。
此时:
cwnd = cwnd / 2;ssthresh = cwnd;- 进入快速恢复。
快恢复
只有当发送端发生快重传时才会使用快恢复算法。
进入快速恢复之前:
-
cwnd = cwnd / 2,也就是设置为原来的一半; -
ssthresh = cwnd。
快速恢复:
-
拥塞窗口 cwnd = ssthresh + 3。
-
重传丢失的数据包。
-
如果再收到重复的 ACK,cwnd + 1。
-
收到新的数据 ACK 后,cwnd 设置成第一步中的 ssthresh 的值,即原来窗口大小的一半。因为此时收到了新的数据 ACK,说明快速恢复已经结束,再次进入拥塞避免状态。