TCP基础知识
TCP头格式
为什么需要TCP
IP 层是「不可靠」的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。就需要由上层(传输层)的 TCP 协议来负责,因为 TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
什么是TCP
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:一定是「一对一」才能连接。(有连接)
- 可靠的:网络链路的变化不影响TCP报文送达接受端。
- 字节流:无论消息多大都可以进行传输,并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃。
什么是TCP连接
连接的定义:用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。
建立一个 TCP 连接是需要客户端与服务器端达成上述三个信息的共识
- Socket:由 IP 地址和端口号组成
- 序列号:确保顺序问题等
- 窗口大小:控制流量
如何唯一确定一个TCP连接
TCP 四元组可以唯一的确定一个连接:
- 源地址
- 源端口
- 目的地址
- 目的端口
源地址和目的地址的字段(32位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。(主机)
源端口和目的端口的字段(16位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程。(端口)
有一个 IP 的服务器监听了一个端口,它的 TCP 的最大连接数是多少?
最大TCP连接数 = 客户端IP数 * 客户端端口数
服务端最大并发 TCP 连接数远不能达到理论上限,会受以下因素影响:
- 文件描述符限制:每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 too many open files。Linux 对可打开的文件描述符的数量分别作了三个方面的限制:
- 系统级:当前系统可打开的最大数量
- 用户级:指定用户可打开的最大数量
- 进程级:单个进程可打开的最大数量
- 内存限制:每个 TCP 连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生 OOM。
UDP 和 TCP 有什么区别呢?
UDP协议非常简单,利用IP提供面向「无连接」的通信服务。
- 目标和源端口:主要是告诉 UDP 协议应该把报文发给哪个进程。
- 该字段保存了 UDP 首部的长度跟数据的长度之和。
- 校验和是为了提供可靠的 UDP 首部和数据而设计,防止收到在网络传输中受损的 UDP包。
区别
应用场景
TCP: FTP文件传输,HTTP/HTTPS
UDP: 视频、音频等多媒体通信
为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?
TCP有「选项」字段且长度可变,UDP的头部长度则是不会变化的。
为什么 UDP 头部有「包长度」字段,而 TCP 头部则没有「包长度」字段呢?
TCP 是如何计算负载数据长度:TCP数据的长度 = IP总长度 - IP首部长度 - TCP首部长度
如果UDP的「包长度」字段,那UDP首部长度就不是4字节的整数倍,不方便计算机处理。
TCP连接建立
TCP三次握手过程和状态变迁
服务端的某个端口处于LISTEN状态,客户端为CLOSE状态
客户端随机生成一个数(client_isn),将TCP首部的「序列号」替换成(client_isn)并且「SYN」值为1,将报文发给服务端。(客户端处于SYN-SEND状态)
服务端将(client_isn + 1)置于「确认号」,并且重新生成一个(server_isn)置于序列号。「ACK」 和 「SYN」标置位置1,将报文发回客户端。(服务端处于SYN-RCVD状态)
客户端将(server_isn + 1)置于「确认号」,「ACK」标志位置为1,发回给服务端。(之后客户端和服务端处于ESTABKLISHED状态)
第三次握手是可以携带数据的,前两次握手是不可以携带数据的
为什么是三次握手?不是两次、四次?
以三个方面分析三次握手的原因:
- 三次握手才可以阻止重复历史连接的初始化(主要原因)
- 三次握手才可以同步双方的初始序列号
- 三次握手才可以避免资源浪费
阻止重复历史连接的初始化
客户端发送SYN建立连接报文,由于网络拥塞会传多个SYN报文:
-
一个「旧报文」比「新报文」先到服务端
-
服务端返回「ACK+SYN」给客户端
-
客户端根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送
RST报文给服务端,表示中止这一次连接。
两次连接就会导致服务端不知道是否断开历史连接并且服务端处于ESTABLISHED状态(可以发送数据),导致服务端的资源浪费。(所以断开连接要在ESTABLISHED状态之前,防止服务端先送数据)
同步双方初始序列号
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
避免资源浪费
如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接.
如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
-
为了防止历史报文被下一个相同四元组的连接接收(主要)
-
为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收
为了防止历史报文被下一个相同四元组的连接接收
既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?
- MTU:一个网络包的最大长度,以太网中一般为
1500字节; - MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。
当接收方发现 TCP 报文(头部 + 数据)的某一片丢失后,则不会响应 ACK 给对方,那么发送方的 TCP 在超时后,就会重发「整个 TCP 报文(头部 + 数据)」,所以重传会再次传输整个报文。
所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。
经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。
三次握手异常情况
第一次握手丢失了,会发生什么?
客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会重发 SYN 报文,在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5,每次超时的时间是上一次的 2 倍
第二次握手丢失了,会发生什么?
客户端就会触发超时重传机制,重传 SYN 报文,服务端这边也会触发超时重传机制,重传 SYN-ACK 报文。
在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定,默认值是 5。
第三次握手丢失了,会发生什么?
服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。
ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
SYN攻击
假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务器不能为正常用户服务。
Linux 内核的 SYN 队列(半连接队列)与 Accpet 队列(全连接队列)
受到 SYN 攻击:
- 如果不断受到 SYN 攻击,就会导致 SYN 队列(半连接队列)被占满,从而导致无法在建立新的连接。
tcp_syncookies的方式可以应对 SYN 攻击
- 当 「 SYN 队列」满之后,后续服务器收到 SYN 包,不进入「 SYN 队列」;
- 计算出一个 cookie 值,再以 SYN + ACK 中的「序列号」返回客户端,
- 服务端接收到客户端的应答报文时,服务器会检查这个 ACK 包的合法性。如果合法,直接放入到「 Accept 队列」。
- 最后应用通过调用 accpet() socket 接口,从「 Accept 队列」取出的连接。
TCP连接断开
TCP 四次挥手过程和状态变迁
- 客户端打算关闭连接,先发送一个
FIN标识位为1的报文,也即FIN报文,之后客户端进入FIN_WAIT_1状态。 - 服务端收到该报文后,就向客户端发送
ACK应答报文,接着服务端进入CLOSED_WAIT状态 - 客户端收到服务端的
ACK应答报文后,之后进入FIN_WAIT_2状态 - 等待服务端处理完数据后,也向客户端发送
FIN报文,之后服务端进入LAST_ACK状态 - 客户端收到服务端的
FIN报文后,回一个ACK应答报文,之后进入TIME_WAIT状态 - 服务器收到了
ACK应答报文后,就进入了CLOSED状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL一段时间后,自动进入CLOSED状态,至此客户端也完成连接的关闭。(为什么等2MSL?防止ack报文丢失,客户端再发一次fin报文在时间范围内)
主动关闭连接的,才有 TIME_WAIT 状态。
为什么挥手需要四次
- 关闭连接时,客户端向服务端发送
FIN时,仅仅表示客户端不再发送数据了但是还能接收数据。 - 服务器收到客户端的
FIN报文时,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN报文给客户端来表示同意现在关闭连接。(先把数据处理完,再断开)
第一次挥手丢失了,会发生什么?
客户端向服务端发送 FIN 报文,然后客户端的连接进入到 FIN_WAIT_1 状态。
正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2状态。
否则客户端触发超时重传机制重传 FIN报文,重发次数由 tcp_orphan_retries 参数控制。
当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,直接进入到 close 状态。
第二次挥手丢失了,会发生什么?
客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
第三次挥手丢失了,会发生什么?
如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。
第四次挥手丢失了,会发生什么?
如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL 是 Maximum Segment Lifetime,报文最大生存时间
TTL 是经过路由跳数
2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。(可以做一次超时重传)
为什么需要 TIME_WAIT 状态?
主动发起关闭连接的一方,才会有 TIME-WAIT 状态。
-
防止历史连接中的数据,被后面相同四元组的连接错误的接收;
- 序列号:是 TCP 一个头部字段,标识了 TCP 发送端到 TCP 接收端的数据流的一个字节,因为 TCP 是面向字节流的可靠协议,为了保证消息的顺序性和可靠性,TCP 为每个传输方向上的每个字节都赋予了一个编号,以便于传输成功后确认、丢失后重传以及在接收端保证不会乱序。序列号是一个 32 位的无符号数,因此在到达 4G 之后再循环回到 0。
- 初始序列号,在 TCP 建立连接的时候,客户端和服务端都会各自生成一个初始序列号,它是基于时钟生成的一个随机数,来保证每个连接都拥有不同的初始序列号。初始化序列号可被视为一个 32 位的计数器,该计数器的数值每 4 微秒加 1,循环一次需要 4.55 小时。
如果没有TIME_WAIT没有或者过短,服务端以相同的四元组重新打开了新连接会接受到历史脏数据。
TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
- 保证「被动关闭连接」的一方,能被正确的关闭;
TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。(有足够的2MSL时间)
TIME_WAIT 过多有什么危害?
- 第一是内存资源占用;
- 第二是对端口资源的占用,一个 TCP 连接至少消耗「发起连接方」的一个本地端口;(6w多端口)
如何优化 TIME_WAIT?
- 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
- 复用处于 TIME_WAIT 的 socket 为新的连接所用。(tcp_tw_reuse 功能只能用客户端(连接发起方),因为开启了该功能,在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用。)
- 引入了时间戳,我们在前面提到的
2MSL问题就不复存在了,因为重复的数据包会因为时间戳过期被自然丢弃。
- net.ipv4.tcp_max_tw_buckets
- 这个值默认为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将后面的 TIME_WAIT 连接状态重置
- 程序中使用 SO_LINGER ,应用强制使用 RST 关闭。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP通过Keepaliva(保活机制)维持连接
- tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
- tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
- tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。
如果开启了 TCP 保活,需要考虑以下几种情况:
第一种,对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。
第二种,对端程序崩溃并重启。当 TCP 保活的探测报文发送给对端后,对端是可以响应的,但由于没有该连接的有效信息,会产生一个 RST 报文,这样很快就会发现 TCP 连接已经被重置。
第三种,是对端程序崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。
如果已经建立了连接,但是客户端的进程崩溃会发生什么?
服务端会发送 FIN 报文,与客户端进行四次挥手。
重传机制
超时重传
一定时间内没收到ACK确认报文,就会重发该数据。
超时时间应该设置为多少呢?(RTT 往返时延)
超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。
- 当超时时间 RTO 较大时,重发就慢,性能差;
- 当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。
超时重传时间 RTO 的值应该略大于报文往返 RTT 的值。
快速重传
快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。
- 由于seq2报文未收到,Seq3 到达了,于是还是 Ack 回 2。
- 后面的 Seq4到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
- 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。
- 最后,收到了 Seq2,此时因为 Seq3,Seq4都收到了,于是 Ack 回 5
重传的时候,是重传之前的一个,还是重传所有的问题。
比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。
SACK
SACK( Selective Acknowledgment 选择性确认)
在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
D-SACK
使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。
- ACK丢包
- 网络延迟
D-SACK 有这么几个好处:
- 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
- 可以知道是不是「发送方」的数据包被网络延迟了;
- 可以知道网络中是不是把「发送方」的数据包给复制了;
滑动窗口
窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值
窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
ACK 300 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方收到了 ACK 400 确认应答,就意味着 400 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。
窗口大小由那一方决定
TCP 头里有一个字段叫 Window,也就是窗口大小。
这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据
发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。
发送方的滑动窗口
程序是如何表示发送方的四个部分的呢?
接受方的滑动窗口
接受窗口和发送窗口的大小是相等的吗?
接收窗口的大小是约等于发送窗口的大小的
当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中窗口字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。(报文通知会有时延)
流量控制
TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制
实际上,发送窗口和接收窗口中所存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整。
服务端系统资源非常紧张的时候,操心系统可能会直接减少了接收缓冲区大小,这时应用程序又无法及时读取缓存数据,那么这时候就有严重的事情发生了,会出现数据包丢失的现象。为了防止这种情况发生,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。
关闭窗口
如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。
关闭窗口潜在的危险
接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了
这会导致发送方一直等待接收方的非 0 窗口通知,接收方也一直等待发送方的数据,如不采取措施,这种相互等待的过程,会造成了死锁的现象
TCP 是如何解决窗口关闭时,潜在的死锁现象呢?
TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器
如果持续计时器超时,就会发送窗口探测 ( Window
probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
- 如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;
- 如果接收窗口不是 0,那么死锁的局面就可以被打破了。
窗口探测的次数一般为 3 次,每次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。
糊涂窗口综合症
如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症
解决方法:
- 让接收方不通告小窗口给发送方
- 让发送方避免发送小数据
怎么让接收方不通告小窗口呢?
小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0
窗口大小 >= MSS,或者接收方缓存空间有一半可以使用,就可以把窗口打开让发送方发送数据过来
怎么让发送方避免发送小数据呢?
使用 Nagle 算法
它满足以下两个条件中的一条才可以发送数据:
- 要等到窗口大小 >=
MSS或是 数据大小 >=MSS - 收到之前发送数据的
ack回包
拥塞控制
为什么要有拥塞控制呀,不是有流量控制了吗?
流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么。
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包
拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络
什么是拥塞窗口?和发送窗口有什么关系呢?
拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的
发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,引入拥塞窗口此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值
- 只要网络中没有出现拥塞,
cwnd就会增大; - 但网络中出现了拥塞,
cwnd就减少;
那么怎么知道当前网络是否出现了拥塞呢?
只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了用拥塞。
拥塞控制有哪些控制算法?
-
慢启动
慢启动的意思就是一点一点的提高发送数据包的数量
当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1
有一个叫慢启动门限
ssthresh(slow start threshold)状态变量。- 当
cwnd<ssthresh时,使用慢启动算法。 - 当
cwnd>=ssthresh时,就会使用「拥塞避免算法」。
- 当
-
拥塞避免
当拥塞窗口
cwnd「超过」慢启动门限ssthresh就会进入拥塞避免算法每当收到一个 ACK 时,cwnd 增加 1/cwnd
- 拥塞发生
-
发生超时重传的拥塞发生算法
ssthresh 和 cwnd 的值会发生变化(1.
ssthresh设为cwnd/2, 2.cwnd重置为1)就重新开始慢启动,慢启动是会突然减少数据流的
-
-
发生快速重传的拥塞发生算法
ssthresh 和 cwnd 的值会发生变化(1.
cwnd = cwnd/2,也就是设置为原来的一半; 2.ssthresh = cwnd; 3.进入快速恢复算法) -
快速恢复
进入快速恢复算法如下
-
塞窗口
cwnd = ssthresh + 3( 3 的意思是确认有 3 个数据包被收到了) -
重传丢失的数据包
-
如果再收到重复的 ACK,那么 cwnd 增加 1
-
如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;
-