HTTP/2特性
现状
自从 HTTP/1.1 发明以来,网站已经发生了很大的变化:
- 一个页面请求的消息大小从几 KB到几 MB
- 每个页面从小于10个资源,到多于100个资源
- 网站内容从文本为主到富媒体为主
- 对页面内容实时性高要求的应用越来越多
高延迟
虽然 HTTP/1.1 默认使用了长连接,但是随着带宽的提高以及网站资源数量的提升,延迟并没有显著下降
HTTP/1.1 的缺点慢慢暴露出来:
- 同一连接同时只能在完成一个 HTTP 事务(请求/响应)才能处理下一个事务
- 即使使用管道网络,但由于服务器的响应是顺序的,存在队头阻塞的问题
- 浏览器对一个域名下的并发连接有限(TCP 连接),Chrome 中同域名下资源加载的最大并发连接数为 6
- 需要重复的传输一些体积巨大的 HTTP 头部,例如用于维护用户状态的 cookie
- 不支持服务器的消息推送
HTTP/1.1 主要的问题就是并发度不够,在单个连接上串行的请求,没有充分的利用带宽,使得随着带宽的增加,延迟并没有显著下降
性能优化
为了提高网站的性能,在 HTTP/1.1 下所做的一些努力:
- 雪碧图,将许多张小图片放到一张大图片中,较少请求
- 将一些文件进行 base64 编码内嵌到 HTML 文件中,减少请求
- 将体积较小的 JavaScript 文件打包成一个大 JavaScript 文件,减少请求
- Sharding 分片,将同一页面的资源分散到不同域名下,提升连接上限
HTTP/2
HTTP/2 的前身是 SPDY 协议,该协议由Google开发,最早在Chromium中提出,HTTP/2 的关键功能主要来自 SPDY 技术。
HTTP/ 2 的提出就是为了解决 HTTP/1.1 没有充分利用带宽的问题。
特点
- HTTP/2 没有改变 HTTP 的基本模型,仅在应用层上修改并充分挖掘 TCP 协议性能
- 支持多路复用,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流
- 支持消息优先级
- 传输的数据量大幅减少,全部采用二进制传输,头部而且会进行压缩
- 服务器可以推送消息
核心概念
- Stream:流是连接中的一个虚拟信道,可以承载双向的消息;
- Message:HTTP/1 中的请求/响应,在 HTTP/2 中是一个逻辑上的概念,对应的就是一系列的帧
- Frame:帧是 HTTP/2 通信的最小单位,承载着特定类型的数据,如 HEAD 帧、DATA 帧
Frame
HTTP/2 将所有传输的信息分割为更小的帧,并采用二进制格式对它们编码。
HTTP/1.1 是明文传输,报文的内容就是文本,而 HTTP/2 是一个彻彻底底的二进制协议。
HTTP/2 所有性能增强的核心在于分帧,网络传输的数据就是一个一个的帧,在抓包的层面,抓取到的每一个 HTTP/2 报文都是一个帧。
帧就是 HTTP/2 的报文,这一点需要和 HTTP/1 严格区分。
帧具有多种类型,就比如:头部帧、数据帧,对应的就是HTTP/1 中的头部和消息体。
HTTP/2 具有很多其他的特性,这些特性往往依托于特定类型的帧实现。
Stream
一个流对应的是 HTTP/1 中的一个事务(一个请求和一个响应),一个流中会有很多的帧,这些帧都属于这次的请求和响应的消息。
在一个 TCP 连接中,会同时存在很多不同的流,流和流之间是乱序到达的,但是属于同一个流的帧之间是有序的。
每个帧都有一个 StreamID 头部,用于标识这个帧属于哪一个流,在到达之后属于同一个流的帧会被组装成消息。
StreamID 是 HTTP/2 实现多路复用的关键,接收端据此实现并发组装消息
- 同一 Stream 内的 frame 必须是有序的
- SETTINGS_MAX_CONCURRENT_STREAMS 控制着并发 Stream 数
- 由客户端主动发送的流的 ID 为奇数
- 在服务器推送的情况下,流的 ID 为偶数
- Stream ID 为 0 的流仅用于传输控制帧
Message
Message 本质上对应的是一个响应 / 请求,在 HTTP/2 中这其实是一个逻辑上的概念。
因为 HTTP/2 传输的数据都是帧,所以本质上一个 Message 是由多个帧(HEAD 帧 + DATA帧)组成。
Message 这个概念在 HTTP/2 中没什么意义,也没有实体存在,只对上层的应用有意义。
多路复用
所谓多路复用,通常表示在一个信道上传输多路信号或数据流的过程和技术。
HTTP/2 实现多路复用的核心就是二进制分帧和流:
有了二进制分帧之后,HTTP/2 不再依赖 TCP去实现多流并行了,发送端可以将 HTTP 消息分解为互不依赖的帧交错发送,最后接收端依据 StreamID 将它们重新组装起来。
多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也更容易实现全速传输(因为TCP具有慢启动的特性)。
多路复用的特点
- 并行交错地发送多个请求,请求之间互不影响
- 并行交错地发送多个响应,响应之间互不干扰
- 消除不必要的延迟和提高带宽的利用率,减少页面加载时间
- 解决了 HTTP/1.1 中存在的队首阻塞问题
帧格式
在 HTTP/2 中,帧本质上就是该层的报文,Type 字段指明了帧的类型,31位的 Stream ID 字段指明了该帧对应的 Stream,Frame Payload 存放该帧附带的数据。
HTTP/2 的很多特性都是通过帧来实现的,所以 HTTP/2 具有多种类型的帧,每个帧都有一个编码类型,对应帧的 Type 字段值。
安全性
IETF 标准不要求 HTTP/2 必须基于 TLS/SSL 协议,但是浏览器要求使用 HTTP/2 必须基于 TLS/SSL 协议。
所以大部分使用 HTTP/2 的网站都是基于 TLS的。
h2: 基于 TLS 协议运行的 HTTP/2 被称为 h2
h2c: 直接在 TCP 协议之上运行的 HTTP/2 被称为 h2c
协议升级
在不使用 TLS 协议进行协议升级的情况,就像升级 websocket 协议一样。
在 TCP 三次握手之后,客户端发送 Connect: Upgrade
报文,然后服务器返回 101 状态码完成升级。
对于 h2c 协议升级发生在 TLS 握手过程中,在 TLS 层 ALPN 扩展做协商,只认 HTTP/1 的代理服务器不会干扰 HTTP/2。
Magic帧
协议升级完成之后还需要发送 Preface,也就是一个 Magic 帧。
- 接收到服务器发送来的 101 Switching Protocols 之后
- TLS 握手成功后
Magic 一般而言都是一个固定的内容,HTTP/2 Preface 内容:0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
解码之后的内容就是 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
SETTING帧
建立起 HTTP/2 连接之后就会发送 SETTING 帧,其对应的 Stream ID 一定为 0
SETTING 帧并不是“协商”,而是发送方向接收方通知其特性、能力
- 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:知会对端头部索引表的最大尺寸,单位字节,基于未压缩前的头部
当 Magic 帧发送完毕之后,客户端紧接着会发一些 SETTING 帧:
头部压缩
HTTP/1 使用文本的形式传输 header,每次都可能重复传输几百到几千的字节,尤其在携带 cookie 的情况下
HTTP/2 使用 HPACK 算法来压缩 HTTP 头部,三种压缩方式:
- 静态字典
- 动态字典
- Huffman 压缩算法
静态字典就是预先定义了一些头部,这些头部 + 取值对一个一个数字,发送时直接使用数字就可以。
动态字典是在请求过程中维护的一张表,将存储的 header 字段与索引值相关联,类似于静态表。
服务器推送
HTTP/2 的服务器推送是指服务器提前将资源推送至浏览器缓存,所以推送是基于已发送的请求。
例如:浏览器在请求 HTML 页面时,服务器在响应 HTML 文件的同时主动推送页面所需的 CSS 文件。
实现服务器推送的方式:
- 推送资源必须对应一个请求(未来的请求)
- 请求信息由服务器端 PUSH_PROMISE 帧发送
- 推送在偶数 ID 的 STREAM 中发送
PUSH_PROMISE 帧的作用是通知客户端服务器即将推送一个资源,这个资源对应未来的一个请求,请先缓存下来
PUSH_PROMISE 帧中包含该资源对应的请求信息,未来的这个请求请求时直接使用缓存即可
当然了资源会在另一个流中推送过来,并且这个推送资源的流和前一个流可以是并发的,而且 PUSH_PROMISE 帧只有服务器才能发送
HTTP/3
HTTP/2 存在的问题
- TCP 以及 TCP+TLS 建立连接握手过多
- 基于 TCP 的 h2c 协议需要先进行三次握手
- 基于 TLS 的 h2 协议需要进行六次握手
- TCP 队头阻塞问题
- HTTP/2 使用单个 TCP 连接
- 流虽然是并发发送的,一旦前面的一个流在网络中丢包了,TCP 的按序到达就会阻塞后面所有的流
可以看到 HTTP/2 存在的问题都是由于 TCP 所引起的,所以 HTTP/3 所解决的问题就是 TCP 对 HTTP/2 的影响
QUIC
QUIC 由 Google 公开,基于 UDP 实现了可靠的报文传输,提高了目前使用 TCP 的面向连接的网络应用的性能
HTTP/3 指的是运行在 QUIC 协议之上的 HTTP,HTTP/ 3 新特性:
- TLS3 升级成了最新的 1.3 版本,握手次数减少
- 允许客户端更换 IP 地址、端口后,仍然可以复用前连接
- 基于 UDP,不存在队头阻塞问题