FumadocsZDecode
网络 & HTTP

HTTP/1 与 HTTP/2

HTTP/2 于 2015 年发布(RFC 7540),在保持 HTTP 语义不变(方法、状态码、头部字段含义均兼容)的前提下,彻底重写了传输层,解决了 HTTP/1.1 的三个根本缺陷。

HTTP/1.x 的痛点

队头阻塞

HTTP/1.1 一个连接同一时刻只能处理一个请求,上一个响应未返回,下一个请求必须等待。

连接 1:[请求A] → 等待响应A → [请求B] → 等待响应B  ← 串行
连接 2:[请求C] → 等待响应C
连接 3:[请求D] → 等待响应D

浏览器的解决方案是同时开 6 个 TCP 连接,但每条连接都需要三次握手和 TLS 握手,开销显著。

Header 重复传输

每次请求都完整发送 Cookie、User-Agent、Accept 等头部。一个页面几十个请求,这些头部几乎全部重复,浪费带宽。

无优先级

协议为纯文本格式,无法给不同请求分配优先级(如 CSS 应优先于图片加载)。

HTTP/1.1 vs HTTP/2

维度HTTP/1.1HTTP/2
协议格式文本二进制分帧
连接数每域名 6 条 TCP1 条 TCP
请求并发多连接模拟并发单连接多路复用
队头阻塞有(应用层)消除应用层,TCP 层仍有
Header 压缩HPACK
请求优先级有(流权重 + 依赖树)
服务端推送有(实践已废弃)
需要 HTTPS实践上是
适用场景请求数少、资源体积大请求数多、资源碎片化

HTTP/2 在请求数多、资源碎片化的场景(典型 SPA 应用)优势明显。大文件下载等请求数少的场景,两者差距不显著。

二进制分帧层

HTTP/2 在 TCP 和 HTTP 语义之间插入了二进制分帧层,把所有通信切成固定格式的帧(Frame):

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                        |
+---------------------------------------------------------------+
  • Type:帧类型,如 HEADERSDATASETTINGSPUSH_PROMISE
  • Stream Identifier:流 ID,标识该帧属于哪个请求/响应
  • Flags:控制标志,如 END_STREAM 表示最后一帧

多路复用

多路复用是 HTTP/2 最核心的特性,基于两个概念:

  • 流(Stream):TCP 连接上的虚拟双向通道,每个请求/响应对独占一个流,客户端发起的流使用奇数 ID
  • 帧(Frame):流的传输单元,同一流的帧按序组装,不同流的帧可交错传输
单条 TCP 连接

├── 流 1(请求 index.html)
│   ├── HEADERS 帧
│   └── DATA 帧(分片 1/3、2/3、3/3)

├── 流 3(请求 style.css)          ← 与流 1 并行
│   ├── HEADERS 帧
│   └── DATA 帧

└── 流 5(请求 script.js)
    ├── HEADERS 帧
    └── DATA 帧

接收端按流 ID 重新组装,一条 TCP 连接替代了 HTTP/1.1 的 6 条连接,消除了每条连接的握手开销和应用层队头阻塞。

TCP 层队头阻塞

多路复用解决了应用层的队头阻塞,但 TCP 是有序可靠传输——底层一个 TCP 包丢失,所有流都必须等待重传,即使其他流的数据已经到达。这是 HTTP/3 切换到 QUIC(基于 UDP)的根本原因。

HPACK 头部压缩

两端各维护一张头部表:

  1. 静态表:预定义 61 个常见头部字段(:method GET:status 200 等)
  2. 动态表:首次出现的头部存入表,后续请求只传索引号(1-2 字节代替几十字节)

Cookie 等又长又重复的字段压缩效果尤其显著。

请求优先级

客户端为每个流设置权重(1–256)和依赖关系,形成优先级树,服务端按权重分配带宽:

根流
├── 流 1(权重 32,CSS)     ← 最优先
│   └── 流 3(权重 16,JS)  ← 依赖 CSS 加载完
└── 流 5(权重 1,图片)     ← 最低优先

服务端推送

服务端可以在客户端未请求的情况下,主动推送资源:

客户端请求 index.html

服务端发现 index.html 依赖 style.css 和 script.js

服务端主动推送(PUSH_PROMISE 帧)

客户端解析 HTML 时资源已在缓存,无需再发请求

实践中 Server Push 容易推送重复资源、绕过浏览器缓存判断,效果不理想。Chrome 已于 2022 年移除对其的支持,HTTP/3 也未纳入此特性。

升级 HTTP/2

需要 HTTPS

RFC 没有强制要求,但主流浏览器只在 TLS 上实现 HTTP/2(通过 ALPN 协商)。实践中 HTTP/2 必须配合 HTTPS。

TLS 握手时,客户端在扩展字段携带 h2,服务端确认后握手完成即切换到 HTTP/2,无需额外 RTT。

无需修改应用代码

HTTP 方法、URL、状态码、Header 语义完全兼容,只需在服务器(Nginx、Node.js 等)开启 HTTP/2 支持。

查看当前协议版本

打开 Chrome DevTools → Network 面板 → 右键列头 → 勾选 Protocol 列。h2 表示 HTTP/2,http/1.1 表示 HTTP/1.1。

多路复用与 WebSocket 的区别

两者解决不同问题,互不替代:

维度HTTP/2 多路复用WebSocket
通信模式请求/响应全双工长连接
发起方客户端发起双方均可主动发送
适用场景并发加载页面资源实时通信(聊天、推送)

On this page