Skip to content

Commit c5fafa1

Browse files
authored
Merge pull request xiaolincoder#61 from youzi-2333/main
修正错误与空格
2 parents 5f1c7c4 + b34ad6b commit c5fafa1

6 files changed

Lines changed: 124 additions & 126 deletions

File tree

‎network/2_http/http2.md‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,30 @@
2121
- *内容形式变多样了*,从单纯到文本内容,到图片、视频、音频等内容;
2222
- *实时性要求变高了*,对页面的实时性要求的应用越来越多;
2323

24-
这些变化带来的最大性能问题就是 **HTTP/1.1 的高延迟**,延迟高必然影响的就是用户体验。主要原因如下几个:
24+
这些变化带来的最大性能问题就是 **HTTP/1.1 的高延迟**,延迟高必然影响的就是用户体验。主要原因如下几个:
2525

2626
- *延迟难以下降*,虽然现在网络的「带宽」相比以前变多了,但是延迟降到一定幅度后,就很难再下降了,说白了就是到达了延迟的下限;
2727
- *并发连接有限*,谷歌浏览器最大并发连接数是 6 个,而且每一个连接都要经过 TCP 和 TLS 握手耗时,以及 TCP 慢启动过程给流量带来的影响;
2828
- *队头阻塞问题*,同一连接只能在完成一个 HTTP 事务(请求和响应)后,才能处理下一个事务;
29-
- *HTTP 头部巨大且重复*,由于 HTTP 协议是无状态的,每一个请求都得携带 HTTP 头部,特别是对于有携带 cookie 的头部,而 cookie 的大小通常很大;
29+
- *HTTP 头部巨大且重复*,由于 HTTP 协议是无状态的,每一个请求都得携带 HTTP 头部,特别是对于有携带 Cookie 的头部,而 Cookie 的大小通常很大;
3030
- *不支持服务器推送消息*,因此当客户端需要获取通知时,只能通过定时器不断地拉取消息,这无疑浪费大量了带宽和服务器资源。
3131

32-
为了解决 HTTP/1.1 性能问题,具体的优化手段你可以看这篇文章「[HTTP/1.1如何优化?](https://xiaolincoding.com/network/2_http/http_optimize.html)」,这里我举例几个常见的优化手段:
32+
为了解决 HTTP/1.1 性能问题,具体的优化手段你可以看这篇文章「[HTTP/1.1 如何优化?](https://xiaolincoding.com/network/2_http/http_optimize.html)」,这里我举例几个常见的优化手段:
3333

3434
- 将多张小图合并成一张大图供浏览器 JavaScript 来切割使用,这样可以将多个请求合并成一个请求,但是带来了新的问题,当某张小图片更新了,那么需要重新请求大图片,浪费了大量的网络带宽;
35-
- 将图片的二进制数据通过 base64 编码后,把编码数据嵌入到 HTML 或 CSS 文件中,以此来减少网络请求次数;
36-
- 将多个体积较小的 JavaScript 文件使用 webpack 等工具打包成一个体积更大的 JavaScript 文件,以一个请求替代了很多个请求,但是带来的问题,当某个 js 文件变化了,需要重新请求同一个包里的所有 js 文件;
35+
- 将图片的二进制数据通过 Base64 编码后,把编码数据嵌入到 HTML 或 CSS 文件中,以此来减少网络请求次数;
36+
- 将多个体积较小的 JavaScript 文件使用 Webpack 等工具打包成一个体积更大的 JavaScript 文件,以一个请求替代了很多个请求,但是带来的问题,当某个 js 文件变化了,需要重新请求同一个包里的所有 js 文件;
3737
- 将同一个页面的资源分散到不同域名,提升并发连接上限,因为浏览器通常对同一域名的 HTTP 连接最大只能是 6 个;
3838

3939

40-
尽管对 HTTP/1.1 协议的优化手段如此之多,但是效果还是不尽人意,因为这些手段都是对 HTTP/1.1 协议的“外部”做优化,**而一些关键的地方是没办法优化的,比如请求-响应模型、头部巨大且重复、并发连接耗时、服务器不能主动推送等,要改变这些必须重新设计 HTTP 协议,于是 HTTP/2 就出来了!**
40+
尽管对 HTTP/1.1 协议的优化手段如此之多,但是效果还是不尽人意,因为这些手段都是对 HTTP/1.1 协议的“外部”做优化,**而一些关键的地方是没办法优化的,比如请求-响应模型、头部巨大且重复、并发连接耗时、服务器不能主动推送等,要改变这些必须重新设计 HTTP 协议,于是 HTTP/2 就出来了!**
4141

4242

4343
---
4444

4545
## 兼容 HTTP/1.1
4646

47-
HTTP/2 出来的目的是为了改善 HTTP 的性能。协议升级有一个很重要的地方,就是要**兼容**老版本的协议,否则新协议推广起来就相当困难,所幸 HTTP/2 做到了兼容 HTTP/1.1 。
47+
HTTP/2 出来的目的是为了改善 HTTP 的性能。协议升级有一个很重要的地方,就是要**兼容**老版本的协议,否则新协议推广起来就相当困难,所幸 HTTP/2 做到了兼容 HTTP/1.1。
4848

4949
那么,HTTP/2 是怎么做的呢?
5050

@@ -56,11 +56,11 @@ HTTP/2 出来的目的是为了改善 HTTP 的性能。协议升级有一个很
5656

5757
## 头部压缩
5858

59-
HTTP 协议的报文是由「Header + Body」构成的,对于 Body 部分,HTTP/1.1 协议可以使用头字段 「Content-Encoding」指定 Body 的压缩方式,比如用 gzip 压缩,这样可以节约带宽,但报文中的另外一部分 Header,是没有针对它的优化手段。
59+
HTTP 协议的报文是由「Header + Body」构成的,对于 Body 部分,HTTP/1.1 协议可以使用头字段 「Content-Encoding」指定 Body 的压缩方式,比如用 gzip 压缩,这样可以节约带宽,但报文中的另外一部分 Header,是没有针对它的优化手段。
6060

6161
HTTP/1.1 报文中 Header 部分存在的问题:
6262

63-
- 含很多固定的字段,比如Cookie、User Agent、Accept 等,这些字段加起来也高达几百字节甚至上千字节,所以有必要**压缩**;
63+
- 含很多固定的字段,比如 Cookie、User Agent、Accept 等,这些字段加起来也高达几百字节甚至上千字节,所以有必要**压缩**;
6464
- 大量的请求和响应的报文里有很多字段值都是重复的,这样会使得大量带宽被这些冗余的数据占用了,所以有必须要**避免重复性**;
6565
- 字段是 ASCII 编码的,虽然易于人类观察,但效率低,所以有必要改成**二进制编码**;
6666

@@ -91,7 +91,7 @@ HTTP/2 为高频出现在头部的字符串和字段建立了一张**静态表**
9191
server: nghttpx\r\n
9292
```
9393

94-
算上冒号空格和末尾的\r\n,共占用了 17 字节,**而使用了静态表和 Huffman 编码,可以将它压缩成 8 字节,压缩率大概 47 %**。
94+
算上冒号空格和末尾的`\r\n`,共占用了 17 字节,**而使用了静态表和 Huffman 编码,可以将它压缩成 8 字节,压缩率大概 47%**。
9595

9696
我抓了个 HTTP/2 协议的网络包,你可以从下图看到,高亮部分就是 `server` 头部字段,只用了 8 个字节来表示 `server` 头部数据。
9797

@@ -133,7 +133,7 @@ HTTP/2 头部由于基于**二进制编码**,就不需要冒号空格和末尾
133133
静态表只包含了 61 种高频出现在头部的字符串,不在静态表范围内的头部字符串就要自行构建**动态表**,它的 Index 从 `62` 起步,会在编码解码的时候随时更新。
134134

135135

136-
比如,第一次发送时头部中的「`user-agent` 」字段数据有上百个字节,经过 Huffman 编码发送出去后,客户端和服务器双方都会更新自己的动态表,添加一个新的 Index 号 62。**那么在下一次发送的时候,就不用重复发这个字段的数据了,只用发 1 个字节的 Index 号就好了,因为双方都可以根据自己的动态表获取到字段的数据**。
136+
比如,第一次发送时头部中的「`User-Agent` 」字段数据有上百个字节,经过 Huffman 编码发送出去后,客户端和服务器双方都会更新自己的动态表,添加一个新的 Index 号 62。**那么在下一次发送的时候,就不用重复发这个字段的数据了,只用发 1 个字节的 Index 号就好了,因为双方都可以根据自己的动态表获取到字段的数据**。
137137

138138
所以,使得动态表生效有一个前提:**必须同一个连接上,重复传输完全相同的 HTTP 头部**。如果消息字段在 1 个连接上只发送了 1 次,或者重复传输时,字段总是略有变化,动态表就无法被充分利用了。
139139

@@ -151,7 +151,7 @@ HTTP/2 头部由于基于**二进制编码**,就不需要冒号空格和末尾
151151

152152
HTTP/2 厉害的地方在于将 HTTP/1 的文本格式改成二进制格式传输数据,极大提高了 HTTP 传输效率,而且二进制数据使用位运算能高效解析。
153153

154-
你可以从下图看到,HTTP/1.1 的响应 和 HTTP/2 的区别:
154+
你可以从下图看到,HTTP/1.1 的响应和 HTTP/2 的区别:
155155

156156
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/http2/二进制帧.png)
157157

@@ -170,7 +170,7 @@ Header: :status: 200 OK 的编码内容为:1000 1000,那么表达的含义
170170
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/network/http/index.png)
171171

172172
1. 最前面的 1 标识该 Header 是静态表中已经存在的 KV。
173-
2. 我们再回顾一下之前的静态表内容,“:status: 200 ok”其静态表编码是8,即1000。
173+
2. 我们再回顾一下之前的静态表内容,“:status: 200 OK”其静态表编码是 8,即 1000。
174174

175175
因此,整体加起来就是 1000 1000。
176176

@@ -227,23 +227,23 @@ HTTP/2 **二进制帧**的结构如下图:
227227

228228
客户端和服务器**双方都可以建立 Stream**,因为服务端可以主动推送资源给客户端, 客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。
229229

230-
比如下图,Stream 1 是客户端向服务端请求的资源,属于客户端建立的 Stream,所以该 Stream 的 ID 是奇数(数字1);Stream 2 和 4 都是服务端主动向客户端推送的资源,属于服务端建立的 Stream,所以这两个 Stream 的 ID 是偶数(数字 2 和4)。
230+
比如下图,Stream 1 是客户端向服务端请求的资源,属于客户端建立的 Stream,所以该 Stream 的 ID 是奇数(数字 1);Stream 2 和 4 都是服务端主动向客户端推送的资源,属于服务端建立的 Stream,所以这两个 Stream 的 ID 是偶数(数字 2 和 4)。
231231

232232
![](https://img-blog.csdnimg.cn/83445581dafe409d8cfd2c573b2781ac.png)
233233

234234
同一个连接中的 Stream ID 是不能复用的,只能顺序递增,所以当 Stream ID 耗尽时,需要发一个控制帧 `GOAWAY`,用来关闭 TCP 连接。
235235

236236
在 Nginx 中,可以通过 `http2_max_concurrent_Streams` 配置来设置 Stream 的上限,默认是 128 个。
237237

238-
HTTP/2 通过 Stream 实现的并发,比 HTTP/1.1 通过 TCP 连接实现并发要牛逼的多,**因为当 HTTP/2 实现 100 个并发 Stream 时,只需要建立一次 TCP 连接,而 HTTP/1.1 需要建立 100 个 TCP 连接,每个 TCP 连接都要经过TCP 握手、慢启动以及 TLS 握手过程,这些都是很耗时的。**
238+
HTTP/2 通过 Stream 实现的并发,比 HTTP/1.1 通过 TCP 连接实现并发要牛逼的多,**因为当 HTTP/2 实现 100 个并发 Stream 时,只需要建立一次 TCP 连接,而 HTTP/1.1 需要建立 100 个 TCP 连接,每个 TCP 连接都要经过 TCP 握手、慢启动以及 TLS 握手过程,这些都是很耗时的。**
239239

240240
HTTP/2 还可以对每个 Stream 设置不同**优先级**,帧头中的「标志位」可以设置优先级,比如客户端访问 HTML/CSS 和图片资源时,希望服务器先传递 HTML/CSS,再传图片,那么就可以通过设置 Stream 的优先级来实现,以此提高用户体验。
241241

242242
## 服务器主动推送资源
243243

244244
HTTP/1.1 不支持服务器主动推送资源给客户端,都是由客户端向服务器发起请求后,才能获取到服务器响应的资源。
245245

246-
比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返,如下图左边部分:
246+
比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返,如下图左边部分:
247247

248248
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/http2/push.png)
249249

@@ -278,7 +278,7 @@ HTTP/2 协议其实还有很多内容,比如流控制、流状态、依赖关
278278

279279
不过,动态表并非可以无限增大, 因为动态表是会占用内存的,动态表越大,内存也越大,容易影响服务器总体的并发能力,因此服务器需要限制 HTTP/2 连接时长或者请求次数。
280280

281-
第二点,**HTTP/2 实现了 Stream 并发**,多个 Stream 只需复用 1 个 TCP 连接,节约了 TCP 和 TLS 握手时间,以及减少了 TCP 慢启动阶段对流量的影响。不同的 Stream ID 可以并发,即使乱序发送帧也没问题,比如发送 A 请求帧1-> B 请求帧1-> A 请求帧2 -> B 请求帧2,但是同一个 Stream 里的帧必须严格有序。
281+
第二点,**HTTP/2 实现了 Stream 并发**,多个 Stream 只需复用 1 个 TCP 连接,节约了 TCP 和 TLS 握手时间,以及减少了 TCP 慢启动阶段对流量的影响。不同的 Stream ID 可以并发,即使乱序发送帧也没问题,比如发送 A 请求帧 1 -> B 请求帧 1 -> A 请求帧 2 -> B 请求帧2,但是同一个 Stream 里的帧必须严格有序。
282282

283283
另外,可以根据资源的渲染顺序来设置 Stream 的**优先级**,从而提高用户体验。
284284

@@ -288,7 +288,7 @@ HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,
288288

289289
**HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。**
290290

291-
有没有什么解决方案呢?既然是 TCP 协议自身的问题,那干脆放弃 TCP 协议,转而使用 UDP 协议作为传输层协议,这个大胆的决定, HTTP/3 协议做了!
291+
有没有什么解决方案呢?既然是 TCP 协议自身的问题,那干脆放弃 TCP 协议,转而使用 UDP 协议作为传输层协议,这个大胆的决定,HTTP/3 协议做了!
292292

293293
![](https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/计算机网络/HTTP/27-HTTP3.png)
294294

0 commit comments

Comments
 (0)