# 计算机网络
# 从浏览器地址栏输入 url 到请求返回发生了什么
- 解析 URL
输入 URL 后,浏览器会解析出协议、主机、端口、路径等信息,并构造一个 HTTP 请求
- 浏览器发送请求前,根据请求头的 expires 和 cache-control 判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源,并不会发送请求。如果没有命中,则进入下一步
- 没有命中强缓存规则,浏览器会发送请求,服务器通过请求头中的 If-Modified-Since 或者 If-None-Match 检查资源是否更新,若资源未更新,返回 304,告诉浏览器直接从缓存获取资源,否则进入下一步
- 如果前两步都没有命中,则返回资源和 200 状态码
- 浏览器缓存
- 强缓存
强缓存就是向浏览器缓存中查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。强缓存又分为两种Expires
和Cache-Control
- 协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
- DNS 域名解析
在发起 http 请求之前,浏览器首先要去获得我们想访问网页的 IP 地址,浏览器会发送一个 UDP 的包给 DNS 域名解析服务器
- 递归查询
我们的浏览器、操作系统、路由器都会缓存一些 URL 对应的 IP 地址,统称为 DNS 高速缓存。这是为了加快 DNS 解析速度,使得不必每次都到根域名服务器中去查询
- 迭代查询
迭代查询的方式就是,局部的 DNS 服务器并不会自己向其他服务器进行查询,而是把能够解析该域名的服务器 IP 地址返回给客户端,客户端会不断地向这些服务器进行查询,直到查询到位置,迭代查询只会帮你找到相关的服务器,然后说我现在比较忙,你自己去找吧
- DNS 负载均衡
DNS 还有负载均衡的作用,现在很多网站都有多个服务器,当一个网站访问量过大的时候,如果所有请求都请求在同一个服务器上,可能服务器就会崩掉,这时候就用到了 DNS 负载均衡技术,当一个网站有多个服务器地址时,在应答 DNS 查询的时候,DNS 服务器会对每个查询返回不同的解析结果,也就是返回不同的 IP 地址,从而把访问引导到不同的服务器上去,来达到负载均衡的目的。例如可以根据每台机器的负载量,或者该机器距离用户的地理位置距离等等条件 - DNS 预解析
大型网站,有多个不同服务器资源的情况下,都可采取 DNS 预解析,提前解析,减少页面卡顿
- TCP/IP 连接:三次握手
客服端和服务端在进行 http 请求和返回的过程中,需要创建 TCP connection(由客户端发起),http 不存在连接这个概念,它只有请求和响应。请求和响应都是数据包,它们之间的传输通道就是 TCP connection
第一次握手:主机 A 发送位码为
SYN=1
,随机产生Seq number=1234567
的数据包到服务器,主机 B 由SYN=1
知道,A 要求建立联机;(第一次握手,由浏览器发起,告诉服务器我要发送请求了)
第二次握手:主机 B 收到请求后要确认联机信息,向 A 发送ACK number=(主机A的Seq+1)
,SYN=1
,随机产生Seq=7654321
的包;(第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧)
第三次握手:主机 A 收到后检查ACK number
是否正确,即第一次发送的Seq number+1
,以及位码SYN
是否为 1,若正确,主机 A 会再发送ACK number=(主机B的seq+1)
,主机 B 收到后确认Seq
值与ACK=7654321+1
则连接建立成功;(第三次握手,由浏览器发送,告诉服务器,我马上就发了,准备接受吧)
为什么需要三次握手,两次不行吗?
其实这是由 TCP 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的接收和发送能力。第一次握手可以确认客服端的发送能力,第二次握手,服务端 SYN=1,Seq=Y 就确认了发送能力,ACK=X+1 就确认了接收能力,所以第三次握手才可以确认客户端的接收能力。不然容易出现丢包的现象
三次握手过程中可以携带数据吗?
其实第三次握手的时候,是可以携带数据的。但是第一次、第二次握手不可以携带数据
为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文
也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病
SYN 攻击?
服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到 SYN 洪泛攻击。SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击
如何防范 SYN 攻击
缩短超时时间
增加最大半连接数
启用 SYN cookies 技术
过滤网关防护
- 发送 HTTP 请求
拓展:HTTPS
在 HTTP 的基础上再加一层 TLS(传输层安全性协议)或者 SSL(安全套接层),就构成了 HTTPS 协议
HTTPS 默认工作在 TCP 协议 443 端口,它的工作流程一般如以下方式:
- TCP 三次同步握手
- 客户端验证服务器数字证书
- DH 算法协商对称加密算法的密钥、hash 算法的密钥
- SSL 安全加密隧道协商完成
- 网页以加密的方式传输,用协商的对称加密算法和密钥加密,保证数据机密性;用协商的 hash 算法进行数据完整性保护,保证数据不被篡改
- 客户端向服务端发送 Client Hello 消息,其中携带客户端支持的协议版本、加密算法、压缩算法以及客户端生成的随机数;
- 服务端收到客户端支持的协议版本、加密算法等信息后;
- 向客户端发送 Server Hello 消息,并携带选择特定的协议版本、加密方法、会话 ID 以及服务端生成的随机数;
- 向客户端发送 Certificate 消息,即服务端的证书链,其中包含证书支持的域名、发行方和有效期等信息;
- 向客户端发送 Server Key Exchange 消息,传递公钥以及签名等信息;
- 向客户端发送可选的消息 Certificate Request,验证客户端的证书;
- 向客户端发送 Server Hello Done 消息,通知服务端已经发送了全部的相关信息;
- 客户端收到服务端的协议版本、加密方法、会话 ID 以及证书等信息后,验证服务端的证书;
- 向服务端发送 Client Key Exchange 消息,包含使用服务端公钥加密后的随机字符串,即预主密钥(Pre Master Secret);
- 向服务端发送 Change Cipher Spec 消息,通知服务端后面的数据段会加密传输;
- 向服务端发送 Finished 消息,其中包含加密后的握手信息;
- 服务端收到 Change Cipher Spec 和 Finished 消息后;
- 向客户端发送 Change Cipher Spec 消息,通知客户端后面的数据段会加密传输;
- 向客户端发送 Finished 消息,验证客户端的 Finished 消息并完成 TLS 握手;
服务端有公钥和私钥,公钥只能由私钥解开。客户端生成的第 2 个随机数为预主密钥,需要用服务端返回的公钥加密发送到服务端。服务端通过私钥解密得到预主密钥。客服端和服务端分别用第 1 随机数、第 2 随机数、预主密钥计算出会话密钥
HTTPS 连接,需要 7 次握手,3 次 TCP + 4 次 TLS
服务器处理请求并返回 HTTP 报文
浏览器渲染页面
断开连接:TCP 四次挥手
- 刚开始双方都处于 established 状态,假如是客户端先发起关闭请求
- 第一次挥手:客户端发送一个
FIN
报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1
状态 - 第二次挥手:服务端收到
FIN
之后,会发送ACK
报文,且把客户端的序列号值+1 作为ACK
报文的序列号值,表明已经收到客户端的报文了,此时服务端处于CLOSE_WAIT
状态 - 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发送
FIN
报文,且指定一个序列号。此时服务端处于LAST_ACK
的状态 - 需要过一阵子以确保服务端收到自己的
ACK
报文之后才会进入CLOSED
状态,服务端收到ACK
报文之后,就处于关闭连接了,处于CLOSED
状态挥手为什么需要四次?
因为当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当服务端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的 FIN 报文我收到了”。只有等到服务端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四次挥手
为什么客户端发送 ACK 之后不直接关闭,而是要等 2MSL 才关闭?
理论上,四个报文都发送完毕,就可以直接进入 CLOSE 状态了,但是可能网络是不可靠的,有可能最后一个 ACK 丢失。所以 TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文。1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端;1 个 MSL 确保对端没有收到 ACK ,重传的 FIN 报文可以到达主动关闭方
详细解析:从输入 URL 开始建立前端知识体系 (opens new window)
# HTTP2 的多路复用
在 HTTP1 中,每次请求都会建立一次 HTTP 连接,也就是我们常说的 3 次握手 4 次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:
- 第一个:串行的文件传输。当请求 a 文件时,b 文件只能等待,等待 a 连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是 1 秒,那么 a 文件用时为 3 秒,b 文件传输完成用时为 6 秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
- 第二个:连接数过多。我们假设 Apache 设置了最大并发数为 300,因为浏览器限制,浏览器发起的最大请求数为 6,也就是服务器能承载的最高并发为 50,当第 51 个人访问时,就需要等待前面某个请求处理完成
HTTP2 采用二进制格式传输,取代了 HTTP1.x 的文本格式,二进制格式解析更高效。多路复用代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都通过同一个 TCP 连接并发完成。在 HTTP1.x 中,并发多个请求需要多个 TCP 连接,浏览器为了控制资源会有 6-8 个 TCP 连接的限制。HTTP2 中
- 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗
- 单个连接上可以并行交错的请求和响应,之间互不干扰
简单版回答:HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应
# A、B 机器正常连接后,B 机器突然重启,A 此时处于 TCP 什么状态
# 问题定义
- A -> B 发起 TCP 请求,A 端为请求侧,B 端为服务侧
- TCP 三次握手已完成
- TCP 三次握手后双方没有任何数据交互
- B 在无预警情况下掉线(类似意外掉电重启状态)
# 问题答案
A 侧的 TCP 链路状态在未发送任何数据的情况下与等待的时间相关,如果在多个超时值范围以内那么状态为established
,如果触发了某一个超时的情况那么视情况的不同会有不同的改变
一般情况下不管是 KeepAlive 超时还是内核超时,只要出现超时,那么必然会抛出异常,只是这个异常截获的时机会因编码方式的差异而有所不同(同步异步 IO,以及有无使用 select、poll、epoll 等 IO 多路复用机制)
# 原因与相关细节
大前提
基于 IP 网络的无状态特征,A 侧系统不会在无动作情况下收到任何通知获知到 B 侧掉线的情况(除非 AB 是直连状态,那么 A 可以获知到自己网卡掉线的异常)
在此大前提的基础上,会因为链路环境、SOCKET 设定、以及内核相关配置的不同,A 侧会在不同的时机获知到 B 侧无响应的结果,但总归是以异常的形式获得这个结果关于内核对待无数据传递 SOCKET 的方式
操作系统有一堆时间超级长的兜底用 timeout 参数,用于在不同的时候给 TCP 栈一个异常退出的机会,避免无效连接过多而耗尽系统资源
其中,TCP KeepAlive
特性能让应用层配置一个远小于内核 timeout 参数的值,用于在这一堆时间超长的兜底参数生效之前,判断链路是否为有效状态关于超时的各个节点
以下仅讨论三次握手成功之后的兜底情况
TCP 链路在建立之后,内核会初始化一个由
nf_conntrack_tcp_timeout_established
参数控制的计时器(这个计时器在 Ubuntu 18.04 里面长达 5 天),以防止在未开启TCP KeepAlive
的情况下连接因各种原因导致的长时间无动作而过度消耗系统资源,这个计时器会在每次 TCP 链路活动后重置TCP 正常传输过程中,每一次数据发送之后,必然伴随对端的 ACK 确认信息。如果对端因为各种原因失去反应(网络链路中断、意外掉电等)这个 ACK 将永远不会到来,内核在每次发送之后都会重置一个由
nf_conntrack_tcp_timeout_unacknowledged
参数控制的计时器,以防止对端意外断网导致的资源过度消耗。(这个计时器在 Ubuntu 18.04 里面是 300 秒/5 分钟)以上两个计时器作为
keepAlive
参数未指定情况下的兜底参数,为内核自保特性,所以事件都很长,建议实际开发与运维中用更为合理的参数覆盖这些数值关于链路异常后发生的操作
A 侧在超时退出之后一般会发送一个 RST 包用于告知对端重置链路,并给应用层一个异常的状态信息,视乎同步 IO 与异步 IO 的差异,这个异常获知的时机会有所不同
B 侧重启之后,因为不存有之前 A-B 之间建立链路相关的信息,这时候收到任何 A 侧来的数据都会以 RST 作为响应,以告知 A 侧链路发生异常
RST 的设计用意在于链路发生意料之外的故障时告知链路上的各方释放资源(一般指的是 NAT 网关与收发两端);FIN 的设计是用于在链路正常情况下的正常单向终止与结束。二者不可混淆
# GET 和 POST 请求区别
- GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有
- GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息
- GET 参数通过 URL 传递,POST 放在 Request body 中
- GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留
- 对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制
# 什么时候有 OPTIONS 请求
# 预检请求头 request header 的关键字段
服务器基于从预检请求头部获得的信息来判断,是否接受接下来的实际请求
- Access-Control-Request-Method:告诉服务器实际请求所使用的 HTTP 方法
- Access-Control-Request-Headers:告诉服务器实际请求所携带的自定义首部字段,本次实际请求首部字段中 content-type 为自定义
# 预检响应头 response header 的关键字段
此次 OPTIONS 请求返回了响应头的内容,但没有返回响应实体 response body 内容
- Access-Control-Allow-Methods:返回了服务端允许的请求,包含 GET/HEAD/PUT/PATCH/POST/DELETE
- Access-Control-Allow-Credentials:允许跨域携带 cookie(跨域请求要携带 cookie 必须设置为 true)
- Access-Control-Allow-Origin:允许跨域请求的域名,这个可以在服务端配置一些信任的域名白名单
- Access-Control-Request-Headers:客户端请求所携带的自定义首部字段 content-type
# 触发 OPTIONS 原因
# OPTIONS 请求自动发起
MDN 的 CORS (opens new window) 一文中提到:
规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求
# 跨域请求时,OPTIONS 请求触发条件
CORS 预检请求触发条件 | 本次请求是否触发该条件 |
---|---|
使用了下面任一 HTTP 方法:PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH | 否,本次为 post 请求 |
人为设置了以下集合之外首部字段:Accept/Accept-Language/Content-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width | 否,未设置其他头部字段 |
Content-Type 的值不属于下列之一:application/x-www-form-urlencoded、multipart/form-data、text/plain | 是,为 application/json |
# 优化 OPTIONS 请求
- Access-Control-Max-Age 表示预请求可以被缓存的最长时间,单位是秒
- 尽量避免不要触发 OPTIONS 请求
# TCP 和 UDP 的区别
# UDP
UDP 协议全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种无连接的协议。UDP 有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。
- 面向无连接
- 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了
- 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
- 有单播,多播,广播的功能
UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能
- UDP 是面向报文的
发送方的 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文。
- 不可靠性
首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。
- 头部开销小,传输数据报文时是很高效的
UDP 头部包含了以下几个数据:
- 两个十六位的端口号,分别为源端口(可选字段)和目标端口
- 整个数据报文的长度
- 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误
因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
# TCP
TCP 协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的 RFC 793 定义。
- 面向连接
面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。
- 仅支持单播传输
每条 TCP 传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。
- 面向字节流
TCP 不像 UDP 一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。
- 可靠传输
对于可靠传输,判断丢包,误码靠的是 TCP 的段编号以及确认号。TCP 为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。
- 提供拥塞控制
当网络出现拥塞的时候,TCP 能够减小向网络注入数据的速率和数量,缓解拥塞。
- TCP 提供全双工通信
TCP 允许通信双方的应用程序在任何时候都能发送数据,因为 TCP 连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP 可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于 MSS)
# TCP 拥塞控制
为什么要进行拥塞控制?假设网络已经出现拥塞,如果不处理拥塞,那么延时增加,出现更多丢包,触发发送方重传数据,加剧拥塞情况,继续恶性循环直至网络瘫痪。
拥塞发生前,可避免流量过快增长拖垮网络;拥塞发生时,唯一的选择就是降低流量。主要使用 4 种算法完成拥塞控制:
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
算法 1、2 适用于拥塞发生前,算法 3 适用于拥塞发生时,算法 4 适用于拥塞解决后
# rwnd 与 cwnd
- rwnd(接收者窗口)是用于流量控制的窗口大小,主要取决于接收方的处理速度,由接收方通知发送方被动调整
- cwnd(拥塞窗口)是用于拥塞处理的窗口大小,取决于网络状况,由发送方探查网络主动调整
# 慢启动算法
慢启动算法
作用在拥塞产生之前:对于刚刚加入网络的连接,要一点一点的提速,不要妄图一步到位。如下:
- 连接建立完成后,一开始初始化
cwnd = 1
,表示可以传一个MSS
大小的数据 - 当收到一个
ACK
确认应答后,cwnd
增加 1,于是一次能够发送 2 个 - 当收到 2 个的
ACK
确认应答后,cwnd
增加 2,于是就可以比之前多发 2 个,所以这一次能够发送 4 个 - 当这 4 个的
ACK
确认到来的时候,每个确认cwnd
增加 1, 4 个确认cwnd
增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个
因此,如果网速很快的话,ACK 返回快,RTT 短,那么,这个慢启动就一点也不慢。下图说明了这个过程
# 拥塞避免算法
当cwnd >= ssthresh
(通常ssthresh = 65535
)时,就会进入拥塞避免算法:缓慢增长,小心翼翼的找到最优值。如下:
- 每收到一个
ACK
,cwnd = cwnd + 1/cwnd
- 每经过一个
RTT
,cwnd++
,线性增长
慢启动算法主要呈指数增长,粗犷型,速度快(“慢”是相对于一步到位而言的);而拥塞避免算法主要呈线性增长,精细型,速度慢,但更容易在不导致拥塞的情况下,找到网络环境的cwnd
最优值
# 拥塞发生时的算法
如果已经发生拥塞,则需要采取策略减小cwnd
。那么,TCP
如何判断当前网络拥塞了呢?很简单,如果发送方发现有Seq
发送失败(表现为“丢包”),就认为网络拥塞了
丢包后,有两种重传方式,对应不同的网络情况,也就对应着两种拥塞发生时的控制算法:
- 超时重传。
TCP
认为这种情况太糟糕,调整力度比较大:
- ssthresh = cwnd/2
- cwnd = 1,重新进入慢启动过程(网络糟糕,要慢慢调整)
- 快速重传。
TCP
认为这种情况通常比超时重传好一些
- ssthresh = cwnd /2
- cwnd = cwnd/2,进入快速恢复算法(网络没那么糟,可以快速调整)
# 快速恢复
如果触发了快速重传,即发送方收到至少 3 次相同的ACK
,那么 TCP 认为网络情况不那么糟,也就没必要提心吊胆的,可以适当大胆的恢复。为此设计快速恢复算法,下面介绍快速恢复的实现
进入快速恢复之前,cwnd 和 sshthresh 已被更新:
- ssthresh = cwnd/2
- cwnd = cwnd/2
然后,进入快速恢复算法:
- 拥塞窗口
cwnd = ssthresh + 3
(3 的意思是确认有 3 个数据包被收到了) - 重传丢失的数据包
- 如果再收到重复的
ACK
,那么cwnd
增加 1 - 如果收到新数据的
ACK
后,把cwnd
设置为第一步中的ssthresh
的值,原因是该ACK
确认了新的数据,说明从duplicated ACK
时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态