网络死亡十连问

2,420 阅读37分钟

不多说,被打击了,好好整理一波: 0010ECE2.png

目录

tcp三次握手四次挥手流程?握手了就不会丢包吗?滑动窗口?队头阻塞?http2怎么解决的?http2的头部压缩怎么压的?服务端推送怎么推的,tcp存在的缺陷?http3了解过吗?在项目中使用过吗......

先来个开胃菜

你项目中用的http协议版本是什么?

http/2是从2015年发布的,我相信很多人会下意识回答 http/2 协议,然而事实真是如此吗?怎么看自己用的协议是什么呢?

HTTP/2协议需要满足以下条件才能开启:

  1. 客户端浏览器和服务器都支持HTTP/2协议(现代浏览器基本都支持
  2. 通过TLS协议(HTTPS)建立的连接。(HTTP/2协议要求所有的传输层连接必须使用TLS,以保证通信安全)

所以说,任何没有运行在安全的https协议的连接都是http1.1

1679996799339.png

我们可以在控制台输入 window.chrome.loadTimes().connectionInfo 查看应用层使用的协议,对于 http2.0 会是 h2,对于 http1.1 会是 http/1.1

image.png

那进入我们今天的学习吧!

知识掌握

OSI七层模型:

OSI七层模型是一个网络通信协议体系,用于描述计算机网络中的不同层次和功能。以下是每个层次及其解析和例子:

物理层(Physical Layer):

该层负责传输二进制数据比特流,以及物理连接、电气规范等硬件特性。例如,通过网线连接两台计算机并传输数字音频或视频流。

数据链路层(Data Link Layer):

该层负责将比特流组成帧,并提供数据的逻辑传输。例如,局域网中使用以太网协议(Ethernet)在计算机之间传输数据。

网络层(Network Layer):

该层负责在不同计算机之间进行路由选择和分组传输。例如,IP(Internet Protocol)协议通过互联网将数据包从源计算机发送到目标计算机。

传输层(Transport Layer):

该层代表了应用层到网络层的连接,负责对传输的数据进行可靠传输和错误检测(建立了主机端到端~端口号的链接)。例如,TCP协议在应用程序之间建立可靠的端到端连接。

那么网络层呢?有什么区别

网络层代表了主机到主机之间的连接,它负责将数据包从源主机发送到目标主机,可能经过多个中间路由器。网络层的主要协议是IP协议,它提供了一种在互联网络中进行主机到主机通信的标准化方式。

与传输层不同,网络层的范围更广泛,涉及跨越网络边界的通信。传输层协议通常仅关注应用程序之间的通信,而网络层协议则负责在整个网络中传输数据。

会话层(Session Layer):

该层负责管理多个进程之间的通信会话,并处理会话的开始、停止和同步(会话层就是负责建立、管理和终止表示层实体之间的通信会话)。例如,SSH(Secure Shell)协议为远程登录会话提供安全加密。

表示层(Presentation Layer):

该层负责数据的格式和编码转换(如base64),以便不同系统间交换信息(确保一个系统的应用层发送的数据能被另一个系统的应用层识别)。例如,在视频会议中使用音频/视频编解码器对媒体进行压缩和解压缩。

应用层(Application Layer):

该层负责处理特定应用程序之间的协议和数据交换。例如,Web浏览器使用HTTP协议从Web服务器上下载网页。

最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP(超文本传输协议)HTTPSFTP(文件传输协议)SMTP(简单邮件传输协议,邮箱验证码就用到了这个) 等。

OSI七层模型通信特点:对等通信 对等通信,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信。

我们常说的 TCP/IP 五层协议就是将应用层、表示层、会话层统定义为应用层

常见的应用层协议有:

  • http是超文本传输协议,定义了浏览器和服务端的通信规则
  • FTP是文件传输协议,将文件从一台计算机上传输到另一台计算机
  • SMTP是简单邮件传输协议,发送邮箱验证码就用到了该协议
  • DNS协议,用于域名与ip之间的映射

http 的协议发展

1678336747046.png

具体可以看我之前的文章:对http、https和代理的一些理解 - 掘金 (juejin.cn)

http 状态码

2xx
  • 200:资源正常处理
  • 201:成功请求并创建了新的资源,该请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回。
  • 202:服务端已收到请求尚未处理,一般用于异步操作场景
  • 204:已处理,响应报文不包含主体,一般用于预检请求
  • 206:服务器端执行了客户端进行了 GET 范围请求。一般用于断点传输(等会讲),响应报文中包含由 Content-Range 指定范围的实体内容
3xx
  • 301:永久重定向,搜索引擎会将旧网址替换为重定向的新网址
  • 302:临时重定向,暂时性修改
  • 304:缓存内容未修改,前端会发起 head 请求(等会讲),根据缓存资源的 Etag 和 if-None-Match 字段返回状态码
4xx
  • 400:bad request,请求报文存在语法错误
  • 401:认证失败,未授权
  • 403:请求资源的访问被服务器拒绝,如在国内直接打开gpt网就会看到403Forbidden,表示因ip地址,或访问次数过多等被服务端拒绝访问
  • 404:资源不存在
  • 405:服务端禁用该方法,可以通过发起预检请求查看服务端支持的方法
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
5xx
  • 502:bad Gateway,一般说明网关接口出错,与客户端无关

http1.0和http1.1的区别:

  • 连接方面,http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接(keep-alive)。
  • http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延(三次握手,等会讲)。
  • 资源请求方面,在 http1.0 中,存在一些浪费带宽的现象,并且不支持断点续传功能,http1.1 则在请求头引入了 range 头域,允许只请求资源的某个部分,这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  • 基于http1.0传输数据,如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了
  • http1.1支持断点传输(等会讲,并给出代码)
  • http1.1 相对于 http1.0 还新增了很多请求方法,如 PUT、HEAD、OPTIONS 等。
  • options请求方法一般用于预检请求,如cors跨域
  • head请求方法与get相似,不返回响应体,只返回响应头。HEAD请求通常用于获取资源的元数据,例如资源的大小、类型、修改时间等信息,可用于协商缓存判断,判断资源是否发生变化

实现http1.1的断点传输功能

HTTP 1.1中的断点传输功能指的是客户端发送带有Range请求头部分的GET请求,服务器会根据请求头中的Range信息返回相应的数据片段,从而实现文件的分段下载,一般断点传输用在大文件(如视频文件)的传输和下载。

  • 请求头部的range字段位于header
  • 响应头部range字段: content-range: bytes=0-1024,客户端根据这个信息来计算出已经下载的数据量,然后在需要续传的时候再向服务器发起请求,支持并发下载

前端部分代码:

const url = 'http://localhost:3000/public/file'; // 请求文件路径
const rangeStart = 0; // 请求的起始字节位置
const rangeEnd = 1024; // 请求的结束字节位置

const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('Range', `bytes=${rangeStart}-${rangeEnd}`);
xhr.responseType = 'arraybuffer';

xhr.onload = function() {
  if (this.status === 206) { // 对于断点传输,服务端会返回206状态码表示支持并响应部分数据流
    const arrayBuffer = xhr.response;
    const blob = new Blob([arrayBuffer], { type: 'video/mp4' });
    const videoUrl = URL.createObjectURL(blob); // 将Blob对象转换为视频URL
    const videoElement = document.createElement('video');
    videoElement.src = videoUrl;
    document.body.appendChild(videoElement);
  }
};
xhr.send();

koa部分代码:

// 处理上传请求
router.post('/upload', async (ctx) => {
  const filePath = 'public/file'; // 文件路径
  const stat = fs.statSync(filePath); // 获取文件状态信息。
  const fileSize = stat.size; // 获取文件大小

  if (ctx.headers.range) { // 判断是否有range请求头
    const parts = ctx.headers.range.replace(/bytes=/, '').split('-');
    const start = parseInt(parts[0], 10);
    const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
    ctx.set({
      'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': end - start + 1,
      'Content-Type': 'video/mp4'
    });
    ctx.status = 206; // 设置状态码为206,表示返回部分数据
    ctx.body = fs.createReadStream(filePath, { start, end }); // 返回部分数据流
  } else {
    ctx.set({
      'Content-Length': fileSize,
      'Content-Type': 'video/mp4'
    });
    ctx.body = fs.createReadStream(filePath); // 返回完整数据流
  }
});

http2的优势

HTTP/1.x是一种基于文本的协议,每个请求和响应都是一个文本流,需要进行多次请求和响应才能完成一个页面的加载。而HTTP/2是一种二进制协议,可以将多个请求和响应打包成一个二进制帧,从而减少了网络传输的次数,提高了页面的加载速度。

HTTP/2支持多路复用,解决了HTTP/1.x存在的队头阻塞问题,使得流量控制更可靠,此外,HTTP/2还支持头部压缩服务器推送等功能,可以进一步减少网络传输的数据量,提高页面的加载速度。

那么问题来了,我刚才标注的名词可以具体说一下嘛 1680527153003.png 且听我分析

文本协议和二进制协议

HTTP/1.x 使用文本格式进行通信,头部信息和数据都需要进行文本解析和序列化,导致传输数据量大,且处理性能低下。

而基于 HTTP/2 的二进制流扩展性更高

// http1的请求格式可能如下:
 GET / HTTP/1.1
Host: www.lhylhylhy.com
Accept: text/html,application/xhtml+xml,application/xml
content-type: application/json
Connection: keep-alive

// http2基于二进制流的请求格式:
Binary Request: 
  +-----------------------------------------------+
  |                 Length (24)                   |
  +---------------+---------------+---------------+
  |   Type (8)    |   Flags (8)   |
  +-+-------------+---------------+-------------------------------+
  |R|                 Stream Identifier (31)                      |
  +=+=============================================================+
  |                   Frame Payload (0...)                      ...
  +---------------------------------------------------------------+

队头阻塞

HTTP/1.x采用的是串行的方式处理请求和响应,即只有前一个请求响应完成之后,才能进行下一个请求的发送。这种方式会导致某些较慢的请求阻塞了后续请求的发送,从而降低了页面加载速度和网络性能。

注:虽然http1.1也提供了分块编码(将正文分为不同块编码进行传输)和管道化请求(允许在一个TCP连接上并行发送请求)的技术一定程度解决了队头阻塞问题,但是仍然存在一些限制。

如分块编码需要服务器在发送响应之前把整个响应都生成出来,然后才能对响应进行分块(对于大文件及不友好),管道化请求中服务器需要维护每个请求的状态

此外,由于HTTP/1.1是基于纯文本协议的,因此请求头部信息较大,占用了很多带宽,进一步增加了请求的传输时间。

而HTTP2优化了队头阻塞所带来的问题:

是优化不是避免,队头阻塞问题是由于底层tcp协议的缺陷带来的 --> 后面讲。

  1. 多路复用:HTTP2允许在同一个TCP连接上同时发送多个请求和响应(并行处理),不需要等待前一个请求响应完成才能进行下一个请求的发送。这种方式可以避免某些较慢的请求阻塞后续请求的发送,提高网络性能和页面加载速度。
  2. 流量控制:HTTP2中引入了流量控制机制,可以对每个数据流进行带宽限制,避免某个数据流占用过多带宽造成其他数据流的阻塞。

通过多路复用和流量控制机制,HTTP2可以更好地利用网络资源,提高页面加载速度和用户体验,从而成为一种更加高效的网络传输协议。

且HTTP2还支持头部压缩和服务器推送等功能,进一步减少了网络延迟和带宽消耗。

乱序重排

数据包发送给客户端会经过一个缓冲区(乱序队列),在这个乱序队列会根据流的序列号对数据进行重新排列,保证数据的有序传输。

多路复用

在同一个TCP连接上同时传输多个请求和响应,http2中的流可以根据需要进行创建和关闭,每个流都有一个唯一的标识符和优先级,可以独立地进行流量控制和错误恢复。多路复用可以将多个请求和响应打包成一个二进制帧,从而减少了网络传输的次数,提高了页面的加载速度。

流量控制

数据会按一定规则排列,防止数据一次性发送到网卡(滑动窗口机制,慢启动机制 ---> 等会讲)

http/2头部压缩

由于HTTP/2自带头部压缩功能,因此无需特别设置即可开启头部压缩功能。使用HPACK压缩算法(HTTP/2静态表和动态表压缩算法),客户端可以在 Accept-Encoding: gzip 告诉服务端支持的压缩方法

有些面试官还会问头部压缩是怎么压的(也就是问HPACK压缩算法),很变态吧!

image.png

  1. 首先,客户端和服务器端会各自维护一个动态表和一个静态表。动态表用于存储最近发送的请求和响应的头部信息,静态表用于存储一些常见的头部信息,如请求方法、状态码等。

  2. 当客户端发送请求时,它会将请求头部信息按照一定的规则编码成二进制格式,并发送给服务器端。服务器端在接收到请求头部信息后,会将其解码成原始的文本格式,并存储到动态表中。

  3. 当服务器端发送响应时,它会将响应头部信息按照一定的规则编码成二进制格式,并发送给客户端。客户端在接收到响应头部信息后,会将其解码成原始的文本格式,并存储到动态表中。

  4. 在编码和解码的过程中,客户端和服务器端会根据一定的规则来管理动态表的大小,以避免过多的头部信息占用过多的内存空间。如果动态表的大小超过了一定的阈值,客户端和服务器端会采取一些策略来清理动态表中的一些头部信息,以保证动态表的大小不会超过阈值。

当然,看不懂吧,先往后面看吧! 00673CD5.gif

http2的服务端推送

HTTP/2服务器推送指的是在客户端请求某个资源时,服务器主动将该资源的相关静态资源一起发送到客户端的机制。避免了重复请求,提高页面加载速度和性能。

看不懂吧,莫慌,来点代码解释一下:

RisingStack/http2-push-example: HTTP/2 Push example (github.com)

// 简单模拟:
const http2 = require('http2')
const server = http2.createSecureServer(
  { cert, key },
  onRequest
)
function push (stream, filePath) {
  const { file, headers } = getFile(filePath)
  const pushHeaders = { [HTTP2_HEADER_PATH]: filePath }
  stream.pushStream(pushHeaders, (pushStream) => {
    pushStream.respondWithFD(file, headers)
  })
}
function onRequest (req, res) {
  // Push files with index.html
  if (reqPath === '/index.html') {
    push(res.stream, '/static/style.css')
    push(res.stream, '/static/script.js')
  }
  // Serve file
  res.stream.respondWithFD(file.fileDescriptor, file.headers)
}

// 假设返回如下内容
`<html>
    <head>
      <link rel="stylesheet" href="/static/style.css">
    </head>
    <body>
      <h1>Hello World</h1>
      <script src="/static/script.js"></script>
    </body>
  </html>
`;

如果没有服务端推送,客户端会在解析index.html文件时向后端发送请求下载css和js文件,也就是需要至少三次请求才能完整获取到内容,而由服务端主动推送静态资源给客户端可以解决重复请求和等待响应的时间(在无设置defer和async的情况下,js的下载会阻塞页面渲染)。

PUSH_PROMISE帧 - HTTP2学习笔记 (skyao.io)

服务端推送是通过 HTTP/2 中的 PUSH_PROMISE 帧来实现的。当客户端请求某个资源时,服务器可以直接在对应的响应帧里发送一个 PUSH_PROMISE 帧,表示将要推送一个相关的资源给客户端。客户端收到这个 PUSH_PROMISE 帧后,可以决定是否需要接受这个推送,并且可以选择拒绝或者推迟接收。

如果客户端选择接受推送,那么服务器就会在当前的连接上,直接将相关的资源推送给客户端。如果客户端不想接受推送,可以发送 RST_STREAM 帧来拒绝该推送(当然,默认客户端不会自动接收服务端推送的数据)。

HTTP/2 服务器推送(Server Push)教程 - 阮一峰的网络日志 (ruanyifeng.com)

也可以用长连接模拟实现服务端推送

这样http1.x也可以模拟实现:

服务器主动向浏览器推送信息,也可以用 WebSocket,还可以用h5的 Server-Sent Events(SSE)

这里不重点讲,后续会更websocket和sse等服务端主动推数据给客户端的文章。

说一下 TCP 三次握手四次挥手发生了什么?

TCP 头格式 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。

确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。

控制位:

  • ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
  • RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
  • SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
  • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。

TCP三次握手是指客户端和服务端需要三次通信来确保TCP连接的成功建立:

  1. 第一次握手:客户端向服务器发送一个SYN(同步)包。该包包含一个随机序列号(ISN),表示客户端的初始序列号。此时,客户端进入SYN_SEND状态,等待服务器的响应,并告诉服务器客户端的接收窗口大小

  2. 第二次握手:服务器收到客户端的SYN包后,会向客户端回复一个SYN-ACK(同步确认)包。该包也包含一个随机序列号(ISN),表示服务器的初始序列号,同时还确认了客户端的SYN包。此时,服务器进入SYN_RECEIVED状态,并也告诉客户端服务器的接收窗口大小

  3. 第三次握手:客户端收到服务器的SYN-ACK包后,会向服务器发送一个ACK(确认)包。该包也包含之前的序列号和确认号,表示客户端已经准备好接收数据。此时,客户端进入ESTABLISHED状态,表示连接已经建立。服务器收到客户端的ACK包后,也进入ESTABLISHED状态,表示连接建立成功。

当这三次握手完成后,TCP连接就建立成功。注意,在TCP连接建立的过程中,每个数据包都需要对方的确认响应,以确保可靠地传输数据。

# TCP三次握手
     Client                  Server

       SYN  -------------->

                          <---------   SYN/ACK

       ACK  -------------->

为什么是三次握手呢?

这是因为在TCP协议中,每个报文段都需要进行确认,而第三次握手的ACK报文段已经包含了对前两次握手的确认,因此不需要再进行第四次握手。如果继续握手,会增加网络的负担,降低网络的效率。

TCP四次挥手确保连接关闭

  1. 客户端发送一个FIN报文段,表示客户端已经没有数据要发送了。

  2. 服务器收到FIN报文段后,发送一个ACK报文段,表示已经收到客户端的关闭请求。

  3. 服务器发送一个FIN报文段,表示服务器已经没有数据要发送了。

  4. 客户端收到FIN报文段后,发送一个ACK报文段,表示已经收到服务器的关闭请求。

在这个过程中,客户端和服务器都需要发送FIN和ACK报文段,总共需要四次通信。

# TCP四次挥手
     Client                  Server

       FIN  -------------->

                          <---------   ACK

                          <---------   FIN

       ACK  -------------->

为什么是4次挥手

百度一面

客户端发送FIN包,服务器回复ACK包,然后服务器发送FIN包,但客户端不回复ACK包,那么服务器会一直等待客户端回复ACK包,从而导致服务器的连接资源被浪费,无法被释放。这种情况被称为"半连接状态",因为服务器已经关闭了连接,但客户端仍然认为连接是打开的。

在正常情况下,客户端在收到服务器的FIN包后应该回复一个ACK包,告诉服务器已经收到了FIN包并同意关闭连接。如果客户端不回复ACK包,服务器会认为连接没有被关闭,会一直等待客户端回复ACK包,从而造成服务器的资源浪费。因此,TCP四次挥手是为了确保连接能够正常关闭,避免半连接状态的出现。

当然,也有一些是将fin和ack一起发送的,也就是三次挥手。面试官跟我说的。

如何保证数据正常接收

我之前一直有个疑问,就是三次握手且保持keepalive长连接,数据的传输在不进行握手的情况,如何保证数据的稳定传输?而且就算握手完也需要发送确认包,为什么不能不握手直接发送确认包? 1680575798171.png

TCP三次握手只是确保连接的建立,无法完全避免数据延迟或丢包等问题。也就是说,tcp三次握手只是确保双方都处于连接的状态。在实际应用中,为了保证数据的可靠传输,TCP协议还采用了一系列的机制,例如超时重传、拥塞控制、流量控制等。这些机制可以有效地减少数据延迟和丢包等问题的发生,从而保证数据的可靠传输。直接发送确认包无法确认发生错误的源头在哪(无法确定是网络问题还是服务端问题)。

  1. 客户端向服务端发送一个HTTP请求,请求中包含了请求头和请求体。请求头包含了请求方法(GET、POST等)、请求路径、请求参数、请求头部等信息。
  2. 服务端收到请求后,解析请求头部,根据请求的路径和方法等信息,处理请求并生成响应数据。响应数据一般包含响应头和响应体,其中响应头包含了响应状态码、响应头部等信息。
  3. 服务端将生成的响应数据发送给客户端。响应数据也分为多个TCP数据包进行传输,每个TCP数据包中包含一个序号和确认号,用于保证数据的可靠传输。
  4. 客户端接收到响应数据后,解析响应头部和响应体,处理响应数据。

TCP重传机制

如果发送方发送的数据包未被接收方正确接收到,接收方会发送一个ACK(确认)数据包告诉发送方要求重传数据。发送方收到ACK后会进行重传,直到接收方正确接收到数据。如果发送方在一定时间内未收到接收方的ACK确认数据包(序列+1),就会进行超时重传。这样保证了TCP协议的可靠传输。

在传输过程中,客户端和服务端通过TCP协议的滑动窗口机制来控制数据包的传输和接收。客户端和服务端都维护了一个发送窗口和一个接收窗口。发送窗口用于控制可发送数据的数量和序号范围,接收窗口用于控制可接收数据的数量和序号范围。

TCP滑动窗口机制

TCP滑动窗口是一种流量控制机制(数据会按一定规则排列,防止数据一次性发送到网卡),用于在数据传输过程中控制发送方和接收方之间的数据流量。数据重传耗费性能,发送窗口和接收窗口来表示数据发送方和接收方的处理数据能力,tcp根据窗口的大小,调整数据发送量,大大降低丢包概率,以避免网络拥塞和数据丢失。

1680577448453.png

具体来说,TCP滑动窗口定义了一个可变大小的发送窗口和接收窗口,用于控制发送方和接收方之间的数据流量。发送方通过检查接收方的滑动窗口大小,控制发送数据的速率和数量;接收方通过检查发送方的滑动窗口大小,控制接收数据的速率和数量。整个过程类似于一个滑动的窗口,不断地调整大小和位置,以适应不同的网络环境和数据传输需求。

TCP滑动窗口的工作流程如下:

  1. 发送窗口:发送方维护一个发送窗口,该窗口的大小是由接收方返回的确认应答中的窗口字段决定>的。发送方只能发送发送窗口中的数据,当发送数据后,发送窗口会向前移动,直到窗口内的所有数据都被确认应答为止。
  2. 接收窗口:接收方维护一个接收窗口,该窗口的大小是根据接收方空闲缓存空间的大小和应用程序的需求进行计算的。接收方只能接收接收窗口内的数据,当接收到数据并发送确认应答后,接收窗口会向前移动,以便接收更多的数据。

简单来说:根据自身能力不断调整窗口的机制,实现流量控制就是滑动窗口机制。

通过使用TCP滑动窗口机制,TCP可以控制数据传输的速率和数量,以适应不同的网络环境和数据传输需求,从而提高数据传输的效率和可靠性。

TCP慢启动

TCP慢启动是一种TCP拥塞控制算法,用于在建立新的TCP连接或恢复中断的TCP连接时,逐渐增加发送数据的速率,以避免网络拥塞和数据包丢失。也就是说:TCP慢启动是一种用于避免网络拥塞和数据包丢失的TCP拥塞控制算法

当TCP连接建立或恢复之后,TCP会首先发送一个较小的数据包(称为初始拥塞窗口),如果该数据包没有发生丢失、重传或超时等问题,则TCP会逐渐增加发送数据的速率,直到达到网络可承受的最大速率为止。这个过程就是TCP慢启动。

具体来讲,TCP慢启动的工作流程如下:

  1. 初始拥塞窗口:当TCP连接建立或恢复时,将设置一个较小的初始拥塞窗口大小,例如2个MSS(最大报文段长度)。这意味着,在开始阶段,TCP只能发送2个MSS大小的数据包。
  2. 拥塞窗口增加:如果接收方确认了所有已发送的数据,则可以将拥塞窗口大小加倍。例如,如果每次成功发送2个MSS的数据包,则拥塞窗口大小会从2个MSS增加到4个MSS。
  3. 网络负载监测:同时,TCP会监测网络的负载情况。如果出现数据包丢失、重传或超时等情况,则表示网络已经拥塞,此时TCP会将拥塞窗口大小减半,并重新开始慢启动过程。
  4. 拥塞窗口限制:在TCP慢启动过程中,如果拥塞窗口大小已经达到了网络可承受的最大带宽,则TCP会进入拥塞避免状态,采用更加稳定的拥塞控制算法进行数据传输。

http1.1能同时使用多少个TCP连接

在HTTP/1.1中,浏览器通常会通过多个TCP连接同时并行下载页面中的不同资源(例如HTML、CSS、JavaScript、图片等)。这样可以提高页面加载速度和性能。

HTTP/1.1规范没有明确规定浏览器可以使用多少个TCP连接。实际上,每个浏览器有其自己的默认设置和限制,而且还可能会受到操作系统、网络环境和服务器配置等因素的影响。

一般来说,现代浏览器对TCP连接数量有一定的限制,以避免过多的连接对网络带宽和服务器性能造成不必要的压力。根据不同的浏览器和版本,同时使用的TCP连接数量通常在6至10个之间(google是6个)。

其中一个阻塞另外五个还能工作吗?

当然可以,多条TCP连接就是为了实现并行下载资源。

多条TCP连接竞争带宽

由于每个TCP连接都会消耗一定的网络带宽和服务器资源,同时使用多个TCP连接会增加网络负载和服务器负担,导致整体的性能下降。

而且当多条TCP连接同时下载资源时,它们之间的带宽分配是由操作系统和网络设备决定的,而非浏览器控制。如果其中某些TCP连接占用了过多的带宽,就会导致其他TCP连接速度变慢甚至阻塞,进而影响整个页面的加载速度和用户体验。

多条TCP连接竞争带宽的解决:

  1. 减少TCP连接数量:通过合理地配置TCP连接数量,避免过度使用TCP连接,从而减小对网络带宽和服务器资源的影响
Connection: keep-alive
Keep-Alive: timeout=60, max=5
  1. 使用http2:http2采用了多路复用技术,可以在同一个TCP连接上并行处理多个请求和响应,且http2有更好的拥塞控制算法,可以避免其中一路占用过多带宽,且支持头部压缩等减小资源大小

用了TCP就一定不会丢包吗?

一个数据包会顺着数据链路层,网络层,传输层,最后从内核空间拷贝到客户端。

在整条链路传输过程,有很多地方可能会发生丢包。如TCP三次握手建立连接时可能会发生丢包,流量层发生丢包

但 TCP 是一种可靠的传输协议,它使用了数据确认、序列号、重传等机制来保证数据的可靠性。当数据包在传输过程中丢失时,TCP 会进行重传,直到接收方确认接收到数据为止。

TCP出现丢包,连接需不需要重传?

肯定需要啊,刚才讲了,且只重传丢失的数据包,从而避免网络带宽的浪费。

UDP了解过吗?用了UDP一定比TCP快吗?

UDP面向无连接,即不需要三次握手四次挥手,不能确保接收方是否可以接收数据。

UDP可以用于视频传输,主要是因为视频传输强调的是实时性和流畅度,而不是数据的可靠性。相对于TCP协议带来的一些延迟和拥塞控制机制,UDP能够提高视频传输的速度和效率。

在视频传输过程中,即使发生数据包丢失,播放器也会尝试通过缓存、重复请求等方式来处理这些问题,从而避免视频播放卡顿或停止。此外,由于视频数据通常是由多个数据包组成的数据流,因此即使某个数据包丢失,也只会影响部分画面的显示,而不会导致整个视频播放失败。

但在如果网络不稳定或拥堵等环境下,UDP会产生较多的丢包现象,从而影响视频的质量和流畅度。因此,视频传输一般用rstp协议(这里不细讲)

http2存在的缺陷?来自协议层还是底层的TCP层?

TCP的缺陷:

  • 队头阻塞;

无法避免(后面讲)只能在应用层面上对请求和响应进行优化,例如采用流量控制、数据压缩、请求优先级等技术,从而提高TCP连接的传输效率,减少队头阻塞问题的出现。

  • TCP 与 TLS 的握手时延迟(https);

慢启动且对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手

  • 网络迁移需要重新连接;

一个 TCP 连接是由四元组(源 IP 地址,源端口,目标 IP 地址,目标端口)确定的,这意味着如果 IP 地址或者端口变动了,就会导致需要 TCP 与 TLS 重新握手,这不利于移动设备切换网络的场景,比如 4G 网络环境切换成 WIFI

无法避免的队头阻塞

HTTP/2 多个请求是跑在一个 TCP 连接中的,那么当 TCP 丢包时,整个 TCP 都要等待重传,那么就会阻塞该 TCP 连接中的所有请求(重传丢失的数据包)。

HTTP/2 协议规范中定义了一个乱序队列缓冲区来处理乱序传输的帧。如果某个帧在传输过程中发生了延迟或丢失,HTTP/2 协议可以将后续的帧继续传输,待该帧到达后再按照顺序进行重新组装。

HTTP/2 中的帧可以乱序传输,但同一个流中的帧需要按照顺序传输,如果前面的帧没有完成传输,后面的帧就会被阻塞。

TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且有序的,如果序列号较低的 TCP 段在网络传输中丢失了,即使序列号较高的 TCP 段已经被接收了,应用层也无法从内核中读取到这部分数据,从 HTTP 视角看,就是请求被阻塞了。

举个例子,如下图:

img

图中发送方发送了很多个 packet,每个 packet 都有自己的序号,你可以认为是 TCP 的序列号,其中 packet 3 在网络中丢失了,即使 packet 4-6 被接收方收到后,由于内核中的 TCP 数据不是连续的,于是接收方的应用层就无法从内核中读取到,只有等到 packet 3 重传后,接收方的应用层才可以从内核中读取到数据,这就是 HTTP/2 的队头阻塞问题,是在 TCP 层面发生的。

为什么同一个流中的帧需要顺序传输

在 HTTP/2 中,一个流是指一条逻辑上的双向通信通道,用于在客户端和服务器之间传输数据。在 HTTP/2 中,同一个流中的所有帧都属于同一个逻辑数据流,并且这些帧都会被打上相同的 Stream Identifier 标记,以便于客户端和服务器可以将它们正确地组装起来。

由于 HTTP/2 中使用了多路复用技术,客户端可以同时发送多个请求,而这些请求都可以使用同一个 TCP 连接来传输。因此,在同一个连接上存在多个流,这些流之间的帧都可能被混杂在一起传输。

为了避免这些帧互相干扰,HTTP/2 规定同一个流中的所有帧必须按照顺序传输。这样,客户端和服务器就可以根据 Stream Identifier 标记将这些帧正确地组装起来,从而还原出原始的请求和响应数据。如果同一个流中的帧乱序传输,那么这些数据将无法正确地被组装起来,就会导致数据的损坏。

  • 在 HTTP/2 中,TCP 传输的数据流被分割成多个数据帧(Frame)进行传输,每个数据帧都包含了一个标识该帧所属的 Stream ID,以及该帧在当前 Stream 中的位置信息(比如该帧是该 Stream 中的第几个帧,该帧的字节偏移量是多少等信息)。

  • TCP 协议会按照传输顺序对帧进行重新排序,以确保它们被按照正确的顺序组装起来。在组装帧时,TCP 会根据每个帧中包含的 Stream ID 和位置信息来确定它们在数据流中的位置关系,从而将它们正确地组装起来。

  • 当 TCP 接收到一个帧时,它会先判断该帧的 Stream ID 是否与当前处理的流的 ID 相同,如果相同,TCP 就会根据该帧中包含的位置信息来确定该帧在当前流中的位置,从而决定该帧应该被放置在数据流的哪个位置。如果该帧的位置信息与当前流中的其他帧位置不一致,TCP 就会将该帧缓存起来,并等待之前的帧到达后再进行组装。

  • 在 HTTP/2 中,由于每个流都有独立的流量控制和优先级控制,因此 TCP 在重新组装帧时,还需要根据每个流的具体情况来调整数据的传输顺序,以保证流量的合理分配。

因此,同一个流需要按照顺序传输,以确保数据的完整性和正确性。

http3为什么好

img HTTP/3是一种新一代的HTTP协议,基于QUIC协议进行传输

基于QUIC协议:HTTP/3使用了QUIC协议进行传输(是基于 UDP 协议在「应用层」实现的协议),相对于TCP协议和TLS协议来说,减少了连接建立的时间和传输数据的延迟。

  • 无队头阻塞;
  • 更快的连接建立;
  • 连接迁移;

无队头阻塞

QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。

由于 QUIC 使用的传输协议是 UDP,UDP 不关心数据包的顺序,如果数据包丢失,UDP 也不关心。

不过 QUIC 协议会保证数据包的可靠性,每个数据包都有一个序号唯一标识。当某个流中的一个数据包丢失了,即使该流的其他数据包到达了,数据也无法被 HTTP/3 读取,直到 QUIC 重传丢失的报文,数据才会交给 HTTP/3。

而其他流的数据报文只要被完整接收,HTTP/3 就可以读取到数据。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。

所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。

img

更快的连接建立

HTTP/3 在传输数据前虽然需要 QUIC 协议握手,这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。

HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果

连接迁移

QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。

总结一下TCP、UDP、QUIC的区别吧

TCP、UDP和QUIC都是网络传输协议

  • TCP是传输控制协议,提供面向连接的、可靠的数据传输服务,具有流量控制、乱序重排和重传机制,保证数据的可靠传输。TCP连接需要经过三次握手建立连接,四次挥手断开连接,连接过程较为复杂,但传输数据稳定可靠。

  • UDP是用户数据报协议,提供无连接的、不可靠的数据传输服务,不具备流量控制、乱序重排和重传机制,传输数据简单高效,但不保证数据的可靠传输。UDP适用于实时性要求较高的应用场景,如视频、音频等。

  • QUIC协议 是基于 UDP 协议在「应用层」实现的协议,它具有类似 TCP 的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠传输的 UDP 协议变成“可靠”的了,所以不用担心数据包丢失的问题。

应用场景

由于TCP具有较强的可靠性和流量控制机制,因此适用于对数据可靠性要求较高的应用场景,如文件传输、电子邮件等;而UDP和QUIC则适用于对实时性要求较高的应用场景,如视频、语音聊天、实时游戏等。

完结撒花

005D823B.gif

对于http3的问题并没有讲的很清楚,真的很麻烦!后面会慢慢更新!

1680581502081.png

最后,给大家看一下我的定时器排序:

const a = [3,10,44,22,120,122,43,12,41,0,1,];
const asyncMessageQueue = []; // 判断异步队列是否为空
const res = []; // 结果
for(let i = 0; i < a.length; i++) {
    asyncMessageQueue.push(setTimeout(() => {
        res.push(a[i]);
        asyncMessageQueue.shift();
    }, a[i]))
}
let timer = setInterval(() => { // 短轮询
    if(asyncMessageQueue.length === 0) {
        console.log(res);
        clearInterval(timer);
    }
}, 500)

别夸 1680581715072.png