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.1 | HTTP/2 |
|---|---|---|
| 协议格式 | 文本 | 二进制分帧 |
| 连接数 | 每域名 6 条 TCP | 1 条 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:帧类型,如
HEADERS、DATA、SETTINGS、PUSH_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 头部压缩
两端各维护一张头部表:
- 静态表:预定义 61 个常见头部字段(
:method GET、:status 200等) - 动态表:首次出现的头部存入表,后续请求只传索引号(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 |
|---|---|---|
| 通信模式 | 请求/响应 | 全双工长连接 |
| 发起方 | 客户端发起 | 双方均可主动发送 |
| 适用场景 | 并发加载页面资源 | 实时通信(聊天、推送) |