HTTP/2特性
HTTP/1.1 的缺点
网站的变化
HTTP/1.1 发明以来:
- 一个页面请求的消息大小从几 KB 到几 MB
- 每个页面从小于 10 个资源,到多于 100 个资源
- 网站内容从文本为主到富媒体为主
- 对页面内容实时性高要求的应用越来越多
出现的问题
HTTP/1.0 一个请求和响应只能跑在一个 TCP 连接上,即每发起一个请求都要新建一次 TCP 连接,而且请求是串行的,通信开销非常的大。
HTTP/1.1 默认长连接(也叫持久连接),只要客户端或服务端没有提出断开连接,则保持 TCP 连接状态,减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。

Connection 头部用于管理持久连接:
- HTTP/1.1 默认长连接,当服务器想断开连接时只需要指定
Connection: close - HTTP/1.1 之前的版本默认短连接,如果想在旧版本也使用就需要指定
Connection: Keep-alive,服务器会返回Connection: keep-alive和Keep-alive: xxxx首部
随着带宽的提高以及网站资源数量的提升,延迟并没有显著下降,HTTP/1.1 的缺点慢慢暴露出来:
-
同一连接同时只能在完成一个 HTTP 事务(请求/响应)才能处理下一个事务
-
浏览器对一个域名下的并发连接有限(TCP 连接),例如 Chrome 同域名下资源加载的最大并发连接数为 6
-
需要重复的传输一些体积巨大的 HTTP 头部,例如用于维护用户状态的 cookie
-
不支持服务器的消息推送
性能优化
为了提高网站的性能,一些优化措施:
- 雪碧图,将许多张小图片放到一张大图片中,较少请求
- 将一些文件进行 base64 编码内嵌到 HTML 文件中,减少请求
- 将体积较小的 JavaScript 文件打包成一个大 JavaScript 文件,减少请求
- Sharding 分片,将同一页面的资源分散到不同域名下,提升连接上限
可以看到 HTTP/1.1 的主要问题就是并发度不够,一个请求需要等到上一个请求处理完毕才能发送。
HTTP/2
HTTP/2 的前身是 SPDY 协议,该协议由 Google 开发,最早在 Chromium 中提出,HTTP/2 的关键功能主要来自 SPDY 技术。
特点
- HTTP/2 没有改变 HTTP 的基本模型,仅在应用层上修改并充分挖掘 TCP 协议性能
- 支持多路复用,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流
- 支持消息优先级
- 传输的数据量大幅减少,全部采用二进制传输,头部而且会进行压缩
- 服务器可以推送消息
核心概念
- Stream:流是连接中的一个虚拟信道,可以承载双向的消息;
- Message:是一个逻辑上的概念,对应的是 HTTP/1 中的请求/响应,本质上是一系列的帧
- Frame:帧是 HTTP/2 通信的最小单位,承载着特定类型的数据,如 HTTP 首部

Frame
HTTP/1.1 是明文传输,报文的内容就是文本。HTTP/2 是一个彻彻底底的二进制协议。
HTTP/1.1 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的帧,并采用二进制格式对它们编码。
HTTP/2 所有性能增强的核心在于分帧。
在使用 HTTP/2 协议时,网络传输的数据就是一个一个的帧。在抓包的层面,抓取到的每一个 HTTP/2 报文都是一个帧。
帧就是 HTTP/2 的报文,这一点需要和 HTTP/1 严格区分。
帧具有多种类型,就比如:头部帧、数据帧,对应的就是 HTTP/1 中的头部和消息体。
因为 HTTP/2 有很多其他的特性,所以也有很多其他类型的帧用于实现其他功能。
Stream
一个流可以看作是 HTTP/1 中的一个事务,也就是一个请求和一个响应。一个流中会有很多的帧,这些帧都是属于这次的请求和响应的消息。
在一次 TCP 连接中,会同时存在很多不同的流,因为 HTTP/2 是支持多路复用的,即允许客户端一直请求,服务端一直响应。所以在到达时流和流之间是乱序到达的。
每个帧都有一个 StreamID 头部,用于标识这个帧属于哪一个流。
由客户端主动发送的流的 ID 为奇数;在服务器推送的情况下,流的 ID 为偶数。
Message
Message 本质上对应的是一个响应 / 请求,在 HTTP/2 中这其实是一个逻辑上的概念。
因为 HTTP/2 传输的数据都是帧,所以本质上 Message 是由传输许多的帧组成。
Message 这个概念在 HTTP/2 中没什么意义,也没有实体存在,只对上层的应用有意义。

多路复用
所谓多路复用,通常表示在一个信道上传输多路信号或数据流的过程和技术。
在 HTTP/2 中,也就是指在一个 TCP 连接中可以同时传输多个 Stream,而一个 Stream 对应的是一个事务。
也就是 HTTP/2 中在一个 TCP 连接中可以并发的发送和接收消息,性能上这是一个极大的升级。
多路复用的核心就是二进制分帧和流(以及流 ID),多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也更容易实现全速传输(因为 TCP 具有慢启动的特性)。
HTTP/2 多路复用的主要思路是:
有了二进制分帧之后,HTTP/2 不再依赖 TCP 去实现多流并行了,客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端依据 StreamID 把它们重新组装起来。
多路复用的特点
- 并行交错地发送多个请求,请求之间互不影响
- 并行交错地发送多个响应,响应之间互不干扰
- 消除不必要的延迟和提高现有网络容量的利用率,减少页面加载时间
- 解决了 HTTP/1.1 中存在的队首阻塞问题

帧的细节
在 HTTP/2 中,帧本质上就是该层的报文,而且二进制分帧是 HTTP/2 性能提升的一个核心。
标准格式
帧的 Type 字段指明了帧的类型,31 位的 Stream ID 字段指明了该帧对应的 Stream。
Frame Payload 存在的就是帧附带的数据。

帧类型
由于 HTTP/2 具有很多的特性,这些特性都是通过帧来实现的,所以就具有很多类型的帧。
每个帧都有一个编码类型,对应帧的 Type 字段值。

SETTING 帧
SETTING 帧对应的是建立起 HTTP/2 连接之后需要发送的帧,SETTING 帧并不是“协商”,而是发送方向接收方通知其特性、能力。
SETTING 帧对应的 Stream ID 为 0。
例如:
- SETTINGS_HEADER_TABLE_SIZE:通知对端索引表的最大尺寸(单位字节,初始 4096 字节)
- SETTINGS_ENABLE_PUSH:设置为 0 时可禁用服务器推送功能,1 表示启用推送功能
- SETTINGS_MAX_CONCURRENT_STREAMS:告诉接收端允许的最大并发流数量
- SETTINGS_INITIAL_WINDOW_SIZE:声明发送端的窗口大小,用于 Stream 级别流控,初始值 字节
- SETTINGS_MAX_FRAME_SIZE:设置帧的最大大小,初始值 2^14 (16,384)字节
- SETTINGS_MAX_HEADER_LIST_SIZE:知会对端头部索引表的最大尺寸,单位字节,基于未压缩前的头部
安全性
有些地方说 HTTP/2 默认使用 HTTPS,这其实是不严谨的。
IETF 标准不要求必须基于 TLS/SSL 协议;但是浏览器要求必须基于 TLS/SSL 协议。
所以大部分使用 HTTP/2 的网站都是基于 TLS 的。
h2: 基于 TLS 协议运行的 HTTP/2 被称为 h2
h2c: 直接在 TCP 协议之上运行的 HTTP/2 被称为 h2c
协议升级
h2c 协议
在不使用 TLS 协议进行协议升级的情况,比较简单类似于升级 websocket 协议一样。
在 TCP 三次握手之后,客户端发送 Connect: Upgrade 报文,然后服务器返回 101 状态码完成升级。
1 | GET / |
1 | 101 Switching Protocols |
h2 协议
对于 h2c 协议升级发生在 TLS 握手过程中,在 TLS 层 ALPN 扩展做协商,只认 HTTP/1 的代理服务器不会干扰 HTTP/2。
Magic 帧
协议升级完成之后还需要发送 Preface,也就是一个 Magic 帧。
发送的时机:
- 接收到服务器发送来的 101 Switching Protocols
- TLS 握手成功后
所谓 Magic 就是一个固定的内容,Preface 内容是一个固定的字符串:
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
解码之后的内容就是 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
当 Magic 帧发送完毕之后,紧接着客户端会发一些 SETTING 帧,之后才会发送数据。

头部压缩
HTTP/1 使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。
HTTP/2 使用 HPACK 算法来压缩 HTTP 头部。
压缩方式:
- 静态字典
- 动态字典
- Huffman 压缩算法
静态字典就是预先定义了一些头部,这些头部 + 取值对一个一个数字,发送时直接使用数字就可以。
动态字典是在请求过程中维护的一张表,将存储的 header 字段与索引值相关联,类似于静态表。

服务器推送
HTTP/2 的服务器推送是指服务器提前将资源推送至浏览器缓存。
例如:浏览器在请求 HTML 页面时,服务器在响应 HTML 文件的同时主动推送页面所需的 CSS 文件。
实现服务器推送的方式:
- 推送资源必须对应一个请求(未来的请求)
- 需要先相应一个 PUSH_PROMISE 帧
- 推送在偶数 ID 的 STREAM 中发送
PUSH_PROMISE 帧
PUSH_PROMISE 帧,type=0x5,只能由服务器发送。

PUSH_PROMISE 帧的作用就是通知客户端即将推送一个资源,并告知该资源对应的请求,后面符合的请求直接使用推送过来的缓存就行。而且推送的流和前一个流是可以并发的。

HTTP/3
HTTP/2 的问题
-
TCP 以及 TCP+TLS 建立连接握手过多
-
队头阻塞
握手过多
基于 TCP 的 h2c 协议需要先进行三次握手,基于 TLS 的 h2 协议需要进行六次握手

队头阻塞
HTTP/2 基于 TCP 且每个请求会复用 TCP,流虽然是并发发送的,但是 TCP 是需要保证资源的按序到达,一旦前面的一个流在网络中丢包了,就会阻塞后面所有的流。

QUIC
QUIC 是由 Google 公开,提高了目前使用 TCP 的面向连接的网络应用的性能,基于 UDP。
QUIC 严格来说运行在 HTTP 协议之下,主要就是基于 UDP 实现了可靠的报文传输。

HTTP/3
HTTP/3 指的是运行在 QUIC 协议之上的 HTTP。
HTTP/ 3 新特性:
- TLS3 升级成了最新的 1.3 版本,握手次数减少
- 允许客户端更换 IP 地址、端口后,仍然可以复用前连接
- 基于 UDP,不存在队头阻塞问题








