HTTP/1.0 与 HTTP/2 是同一套网络协议在不同时代的两次重要形态。理解它们的差异,不只是记住几个新特性,更是理解 Web 性能优化思路如何从"在应用层打补丁"走向"在协议层重构"。
从 HTTP/1.0 说起
HTTP/1.0 的核心模型非常简单:一个 TCP 连接处理一个请求。
客户端 --建立TCP连接--> 服务端
客户端 --发送请求------> 服务端
服务端 --返回响应------> 客户端
客户端 --关闭TCP连接---> 服务端
这个模型有几个明显的代价:
- 连接开销大:每个请求都要经历 TCP 三次握手,服务器压力大,延迟高。
- 队头阻塞(Head-of-Line Blocking):同一个连接上,前一个请求没响应完,后面的请求只能等。
- 无状态:服务端不记录上一次请求的信息,每次都要重新携带完整上下文。
HTTP/1.1 的出现缓解了一部分问题,引入了持久连接(keep-alive)、管道化(pipelining,实际很少使用)、分块传输等。但受限于协议本身,队头阻塞依然存在——即使管道化,响应也必须按请求顺序返回。
于是浏览器和开发者被迫在应用层做各种优化:域名分片、雪碧图、资源合并、内联脚本。这些手段本身,就是在对抗 HTTP/1.x 的协议缺陷。
HTTP/2 带来了什么
HTTP/2 不是推倒重来,而是在语义层保持兼容(方法、状态码、头部字段含义不变),在传输层彻底重构。
1. 二进制分帧
HTTP/1.x 的报文是纯文本,而 HTTP/2 把所有通信拆成二进制帧(Frame)。帧是 HTTP/2 的最小传输单位,多个帧可以交错发送,接收端再按流 ID 重新组装。
文本协议的好处是可读、易调试,但对机器而言解析效率低、边界模糊。二进制分帧让协议更紧凑、更严谨,也为后续的多路复用打下基础。
2. 多路复用
这是 HTTP/2 最核心的改进。在同一条 TCP 连接上,可以同时并行处理多个请求和响应,每个请求/响应对应一个独立的"流(Stream)"。
HTTP/1.1: 连接1 --> 请求A --> 等待 --> 响应A --> 请求B ...
HTTP/2: 连接1 --> 流1(请求A) / 流3(请求B) / 流5(请求C) 并行交错
多路复用直接解决了 HTTP/1.x 的队头阻塞问题,也意味着不再需要域名分片、资源合并这些"歪招"。
3. 头部压缩(HPACK)
HTTP/1.x 每次请求都要重复发送大量头部(Cookie、User-Agent 等),浪费带宽。HTTP/2 使用 HPACK 算法:
- 用静态表 + 动态表把常见头部字段映射为索引;
- 用哈夫曼编码压缩字面量。
对于高频重复的头部,往往只需几个字节就能表达。
4. 服务端推送(Server Push)
服务端可以在客户端请求 HTML 时,主动把关联的 CSS、JS 一起推过去,减少往返延迟。不过由于缓存和优先级处理复杂,实际落地效果有限,如今在 HTTP/3 中已被弱化乃至移除。
5. 请求优先级
客户端可以为不同的流设置优先级和依赖关系,让服务端优先返回关键资源。但各实现对其支持不一,效果也不稳定。
两者的核心差异对照
| 维度 | HTTP/1.0(及 1.1) | HTTP/2 |
|---|---|---|
| 传输格式 | 文本 | 二进制分帧 |
| 连接使用 | 每请求一连接(1.0);1.1 持久连接 | 单连接多路复用 |
| 并发能力 | 依赖多连接,受队头阻塞限制 | 单连接内并行流 |
| 头部传输 | 明文重复发送 | HPACK 压缩 |
| 服务端推送 | 不支持 | 支持(Server Push) |
| 优化思路 | 应用层:合并、分片、内联 | 协议层:多路复用、压缩 |
HTTP/2 仍然存在的问题
HTTP/2 解决了应用层的队头阻塞,但没有解决 TCP 层的队头阻塞。因为所有流共享一条 TCP 连接,一旦某个 TCP 报文丢失,后续所有流都要等待重传——这就是 TCP 层队头阻塞。
这正是 HTTP/3 改用 QUIC(基于 UDP)的动机:把流控制下沉到传输层,单个流丢包不再阻塞其他流。
小结
- HTTP/1.0 简单但低效,每个请求一条连接,队头阻塞严重。
- HTTP/1.1 用持久连接缓解问题,但没有根治。
- HTTP/2 通过二进制分帧、多路复用、头部压缩,把优化从应用层搬回了协议层。
- HTTP/2 的遗留问题是 TCP 层队头阻塞,这成为 HTTP/3 的起点。
理解这条演进线,比孤立地记特性更有价值:每一次协议升级,都是在回答"上一个时代被迫用工程手段绕开的问题,能不能在协议里直接解决"。