mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
4841 字
13 分钟
HTTP协议演进:从1.0到3.0的性能革命
2022-08-22

TLS与安全通道 中,看到了 TLS 如何在 TCP 之上构建加密通道——握手完成之后,应用层协议就可以在这个通道上安全传输数据。这个应用层协议,绝大多数情况下就是 HTTP。

但 HTTP 并非一成不变。从 1991 年 Tim Berners-Lee 写下的那个只能获取 HTML 的简单协议,到今天 HTTP/3 基于 QUIC 的多流并发传输,HTTP 经历了三十多年的演进。每一次版本更迭都指向同一个目标:减少延迟,提高连接利用率

本章从 HTTP/0.9 的极简设计出发,逐代分析每个版本要解决的问题、引入的机制和遗留的缺陷,最后通过动手实践观察不同 HTTP 版本的真实行为差异。

一、HTTP/0.9 与 HTTP/1.0#

1.1 HTTP/0.9:一行协议#

1991 年的 HTTP/0.9 简单到令人难以置信:客户端发送一行 ASCII 文本作为请求,服务器返回 HTML 文档作为响应,然后连接关闭。

GET /index.html

没有请求头,没有响应头,没有状态码,没有版本号。服务器只能返回 HTML,不支持其他内容类型。请求-响应-断开,就这么简单。

这种极简设计在当时完全够用——万维网只是 CERN 内部的文档共享系统,页面里嵌几张图片都算奢侈。但很快,人们需要传输图片、表单、视频,需要告诉客户端”这个响应是什么格式”,需要区分成功和失败。

1.2 HTTP/1.0:补上缺失的元数据#

1996 年的 HTTP/1.0(RFC 1945)给请求和响应加上了头部字段,让协议具备了表达元数据的能力:

GET /index.html HTTP/1.0
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 1234
<html>...

关键改进包括:

  • 版本号:请求行中标注 HTTP/1.0,让通信双方明确协议版本
  • 状态码:响应行包含状态码和原因短语(如 200 OK404 Not Found
  • 请求头与响应头Content-Type 标识响应体的媒体类型,Content-Length 标识响应体长度
  • 通用头部DateServerLast-Modified 等提供缓存和元信息支持

但 HTTP/1.0 有一个致命问题:每个请求都需要建立新的 TCP 连接。一次 TCP 连接只能传输一个请求-响应对,传输完毕后连接关闭。一个典型网页可能包含几十个资源——HTML、CSS、JavaScript、图片——每个资源都要经历完整的 TCP 三次握手和 TLS 握手(如果是 HTTPS),延迟开销巨大。

Note

HTTP/1.0 规范本身没有定义持久连接,但很多实现支持非标准的 Connection: Keep-Alive 头部来复用连接。这只是一个临时补丁,不是协议的正式行为。

二、HTTP/1.1 持久连接#

2.1 Keep-Alive:连接复用#

HTTP/1.1(RFC 2068,后更新为 RFC 2616)最核心的改进就是持久连接(persistent connection)——默认情况下 TCP 连接在请求-响应完成后不关闭,后续请求可以复用同一个连接。

GET /style.css HTTP/1.1
Host: www.example.com
Connection: keep-alive
HTTP/1.1 200 OK
Content-Type: text/css
Content-Length: 5678
Connection: keep-alive
body { ... }

同一个 TCP 连接上,客户端可以连续发送多个请求,省去了重复握手的开销。HTTP/1.1 默认启用持久连接,只有显式发送 Connection: close 才会断开。

持久连接带来的收益是显著的:

场景HTTP/1.0(每请求新建连接)HTTP/1.1(持久连接)
加载 6 个资源6 次 TCP 握手 + 6 次 TLS 握手1 次 TCP 握手 + 1 次 TLS 握手
首个资源延迟1 RTT(TCP)+ 2 RTT(TLS 1.2)+ 1 RTT(HTTP)同左
后续资源延迟同上(每次重新握手)仅 1 RTT(HTTP 请求-响应)

2.2 管道化:理想与现实的差距#

持久连接解决了连接复用问题,但请求-响应仍然是串行的——客户端必须等上一个响应返回后才能发送下一个请求。如果第一个请求的响应慢了,后续所有请求都得排队。

HTTP/1.1 引入了管道化(pipelining):允许客户端在收到前一个响应之前就发送下一个请求,将多个请求”管道”式地推到服务器端:

sequenceDiagram participant C as 客户端 participant S as 服务器 Note over C,S: HTTP/1.1 管道化 C->>S: 请求 A C->>S: 请求 B(不等 A 的响应) C->>S: 请求 C(不等 A、B 的响应) S-->>C: 响应 A S-->>C: 响应 B S-->>C: 响应 C Note over C,S: 响应必须按请求顺序返回!

管道化看起来很美,但实际部署中几乎没人用。问题出在队头阻塞(Head-of-Line Blocking)——服务器必须按请求的顺序返回响应。如果请求 A 的处理耗时 500ms,请求 B 和 C 即使已经处理完毕,也必须等 A 的响应发完才能发送。

更糟糕的是,中间代理和 CDN 对管道化的支持参差不齐,导致实际部署中经常出现请求丢失、响应错乱等问题。大多数浏览器最终选择禁用管道化,转而采用多连接策略——浏览器对同一域名同时打开 6 个 TCP 连接,用连接级别的并行来绕过请求级别的串行。

2.3 其他重要改进#

HTTP/1.1 还引入了几个影响深远的特性:

Host 头部:允许同一 IP 地址上托管多个域名(虚拟主机),这是现代共享主机和 CDN 的基础:

GET /index.html HTTP/1.1
Host: www.example.com

分块传输编码(Chunked Encoding):当响应体在生成时无法确定总长度时,服务器可以分块发送,每块前标注该块长度:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
7\r\n
Mozilla\r\n
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n
\r\n

缓存机制ETagIf-None-MatchCache-Control 等头部提供了更精细的缓存控制,减少了不必要的重复传输。

三、HTTP/2 多路复用#

3.1 从文本到二进制:帧层重构#

HTTP/1.x 的消息是纯文本格式,用换行符分隔头部和正文。这种格式对人眼友好,但对解析器不友好——需要处理各种边界情况(行结束符、头部折叠、编码问题等)。

HTTP/2 彻底重构了消息的传输格式,引入了二进制帧层(binary framing layer)。所有通信都在帧(frame)中进行,帧有固定的格式:

+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) |
+---------------------------------------------------------------+
字段长度说明
Length24 bit帧载荷长度(不含帧头)
Type8 bit帧类型(DATA、HEADERS、SETTINGS 等)
Flags8 bit帧标志位(含义取决于帧类型)
R1 bit保留位,必须为 0
Stream Identifier31 bit流标识符,标识帧属于哪个流
Frame Payload可变帧载荷内容

HTTP/2 定义了以下核心帧类型:

帧类型Type 值用途
DATA0x0传输请求/响应体数据
HEADERS0x1传输请求/响应头部(含伪头部)
PRIORITY0x2指定流的优先级和依赖关系
RST_STREAM0x3终止一个流
SETTINGS0x4通信双方的配置参数协商
PUSH_PROMISE0x5服务器推送资源的承诺
PING0x6往返延迟测量与存活检测
GOAWAY0x7通知对端停止创建新流
WINDOW_UPDATE0x8流控窗口更新
CONTINUATION0x9HEADERS 帧的续帧

3.2 流、消息与帧#

HTTP/2 引入了三个核心概念:

  • 帧(Frame):HTTP/2 通信的最小单位,每个帧都属于某个流
  • 消息(Message):一个完整的请求或响应,由一个或多个帧组成
  • 流(Stream):连接内的双向字节流,承载一条完整的请求-响应消息

一个 TCP 连接上可以同时存在多个流,每个流有唯一的流 ID。客户端发起的流使用奇数 ID,服务器发起的流使用偶数 ID。不同流的帧可以交错发送,接收端根据流 ID 重新组装:

flowchart LR subgraph 客户端 R1["请求 A<br/>Stream 1"] R2["请求 B<br/>Stream 3"] R3["请求 C<br/>Stream 5"] end subgraph TCP连接["TCP 连接(二进制帧流)"] direction LR F1["H1<br/>Stream 1"] F2["H3<br/>Stream 3"] F3["D1<br/>Stream 1"] F4["H5<br/>Stream 5"] F5["D3<br/>Stream 3"] F6["D1<br/>Stream 1"] F7["D5<br/>Stream 5"] F8["D3<br/>Stream 3"] end subgraph 服务器 S1["响应 A<br/>Stream 1"] S2["响应 B<br/>Stream 3"] S3["响应 C<br/>Stream 5"] end R1 & R2 & R3 --> TCP连接 TCP连接 --> S1 & S2 & S3 style TCP连接 fill:#e8f5e9,stroke:#2e7d32

与 HTTP/1.1 管道化的关键区别:流之间相互独立,不需要按顺序返回。Stream 3 的响应可以先于 Stream 1 完成,客户端根据流 ID 将帧分配到对应的流中重组,互不干扰。

3.3 HPACK 头部压缩#

HTTP/1.x 的头部是纯文本,每次请求都要重复发送大量相同的头部字段(如 User-AgentCookieAccept),浪费带宽。一个典型的 HTTP/1.1 请求头部可能超过 800 字节,其中大部分内容与上一个请求完全相同。

HTTP/2 引入了 HPACK 头部压缩算法(RFC 7541),核心思路是:

  1. 静态表:预定义 61 个常见头部字段(如 :method: GET:path: /content-type: text/html),用索引号替代完整字符串
  2. 动态表:通信双方各自维护一个 FIFO 队列,记录之前发送过的头部字段,后续请求只需发送索引号
  3. 哈夫曼编码:对字符串值进行哈夫曼编码,进一步压缩
# HTTP/1.1:每次请求发送完整头部(约 800 字节)
:method: GET
:path: /style.css
:scheme: https
host: www.example.com
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
accept: text/css,*/*;q=0.1
accept-language: zh-CN,zh;q=0.9,en;q=0.8
cookie: session_id=abc123def456ghi789...
# HTTP/2 + HPACK:大部分头部用索引号替代(约 50 字节)
:method: GET → 静态表索引 2
:path: /style.css → 动态表索引 62(假设已缓存)
:scheme: https → 静态表索引 7
host: www.example.com → 动态表索引 63
cookie: ... → 增量编码 + 哈夫曼

3.4 服务器推送#

HTTP/2 的**服务器推送(Server Push)**允许服务器在客户端请求之前主动发送资源。当客户端请求 index.html 时,服务器知道页面还需要 style.cssapp.js,可以主动推送这些资源,省去客户端解析 HTML 后再发起请求的往返:

# 客户端请求
GET /index.html HTTP/2
# 服务器响应 index.html 的同时推送关联资源
PUSH_PROMISE Stream 2: /style.css
PUSH_PROMISE Stream 4: /app.js
HEADERS Stream 1 → index.html 响应
DATA Stream 1 → index.html 内容
HEADERS Stream 2 → style.css 响应
DATA Stream 2 → style.css 内容
HEADERS Stream 4 → app.js 响应
DATA Stream 4 → app.js 内容

不过服务器推送在实践中争议很大——服务器很难准确判断客户端是否真的需要推送的资源(客户端可能已有缓存),推送不当反而浪费带宽。Chrome 后来在 HTTP/2 中默认禁用了服务器推送,HTTP/3 更是将其移除。

3.5 流优先级#

HTTP/2 允许客户端为每个流指定优先级(priority)依赖关系(dependency),告诉服务器哪些资源更重要,应该优先发送。例如,CSS 的优先级高于图片,关键渲染路径上的资源优先级高于懒加载资源:

# PRIORITY 帧示例
Stream 3 依赖 Stream 1,权重 256(高优先级——CSS)
Stream 5 依赖 Stream 1,权重 16(低优先级——图片)
Stream 7 依赖 Stream 3,权重 128(中优先级——JavaScript)

优先级信息通过 PRIORITY 帧传递,形成一棵依赖树。服务器根据这棵树调度帧的发送顺序。但优先级只是”建议”,服务器可以选择忽略——实际部署中,很多服务器和 CDN 对优先级的支持并不完善。

四、HTTP/2 的队头阻塞#

3.1 TCP 层的队头阻塞#

HTTP/2 的多路复用解决了应用层的队头阻塞——多个流可以交错发送,不再需要按顺序返回响应。但所有流都复用同一条 TCP 连接,传输层的队头阻塞依然存在。

当 TCP 连接上发生丢包时,问题就暴露了:

sequenceDiagram participant C as 客户端 participant N as 网络 participant S as 服务器 Note over C,S: HTTP/2 多路复用:所有流共享一条 TCP 连接 C->>N: Stream 1 帧 A C->>N: Stream 3 帧 B (丢包) C->>N: Stream 5 帧 C C->>N: Stream 1 帧 D N->>S: 帧 A 到达 Note over S: 帧 C、D 已到达但 TCP 层无法交付<br/>因为帧 B(序号在它们之前)丢失了 S-->>C: ACK 确认帧 A,报告帧 B 丢失 C->>N: 重传帧 B N->>S: 帧 B 到达,TCP 层现在可以交付 B、C、D

TCP 保证字节流的有序交付。一旦某个段丢失,后续所有已到达的段都必须在 TCP 接收缓冲区中等待,直到丢失的段重传成功。对于 HTTP/2 来说,这意味着一个流的丢包会阻塞所有流的交付——即使 Stream 5 的数据已经完整到达,只要 Stream 3 的某个 TCP 段丢了,Stream 5 的数据也无法被应用层读取。

4.2 丢包放大的影响#

在 HTTP/1.1 中,6 个 TCP 连接中某个连接丢包,只影响那一个连接上的请求。但在 HTTP/2 中,所有请求共享一个连接,一次丢包的影响被放大到所有流上。

丢包率越高,HTTP/2 的性能退化越严重。在高丢包率环境(如移动网络,丢包率 1%-5%)下,HTTP/2 的性能可能反而不如 HTTP/1.1 的多连接方案。Google 2017 年的测试数据显示,在 1% 丢包率下,HTTP/2 的页面加载时间比 HTTP/1.1 慢约 10%-20%。

这就是 HTTP/2 的根本矛盾:多路复用解决了应用层队头阻塞,却让传输层队头阻塞的影响范围从单个请求扩大到了所有请求

五、HTTP/3 over QUIC#

5.1 换掉 TCP#

HTTP/3 的核心决策是将传输层从 TCP 换成 QUIC。QUIC 在 QUIC与HTTP/3 中已有详细讨论,这里聚焦它如何解决 HTTP/2 的队头阻塞问题。

QUIC 的流是真正独立的——每个流有自己的流级序号和确认机制。一个流的丢包不会影响其他流的数据交付:

flowchart TB subgraph HTTP2["HTTP/2 over TCP"] direction TB TCP_H2["TCP 连接<br/>(单一字节流)"] S1_H2["Stream 1"] S2_H2["Stream 3"] S3_H2["Stream 5"] S1_H2 & S2_H2 & S3_H2 --> TCP_H2 Note_H2["Stream 3 丢包 →<br/>Stream 1、5 全部阻塞"] end subgraph HTTP3["HTTP/3 over QUIC"] direction TB QUIC_H3["QUIC 连接<br/>(多流独立传输)"] S1_H3["Stream 1<br/>独立流控"] S2_H3["Stream 3<br/>独立流控"] S3_H3["Stream 5<br/>独立流控"] S1_H3 & S2_H3 & S3_H3 --> QUIC_H3 Note_H3["Stream 3 丢包 →<br/>Stream 1、5 不受影响"] end style HTTP2 fill:#ffebee,stroke:#c62828 style HTTP3 fill:#e8f5e9,stroke:#2e7d32

QUIC 在 UDP 之上实现了自己的可靠传输机制。每个 QUIC 流维护独立的包序号空间,丢包重传只影响丢包所在的流,其他流可以继续交付数据。

5.2 QPACK 头部压缩#

HTTP/3 用 QPACK(RFC 9204)替代了 HPACK。两者的核心思路相同(静态表 + 动态表 + 哈夫曼编码),但设计上有重要差异:

特性HPACK(HTTP/2)QPACK(HTTP/3)
静态表条目数6199(新增 :status 421 等)
动态表更新与数据帧交错发送通过专用控制流发送
编码阻塞风险存在(动态表引用需按序确认)降低(使用绝对索引,减少阻塞)
确认机制隐式(TCP 有序交付保证)显式(INSERT_COUNT、ACK)

HPACK 依赖 TCP 的有序交付来保证动态表状态一致。但 QUIC 的流是独立的,无法依赖传输层的顺序保证。QPACK 通过引入显式确认机制和绝对索引来解决这个问题——动态表的插入需要接收方显式确认后才能被引用,避免了状态不一致。

5.3 连接迁移#

TCP 连接由四元组 (源IP, 源端口, 目的IP, 目的端口) 标识。当用户从 WiFi 切换到 4G 时,源 IP 地址改变,TCP 连接断开,必须重新握手。

QUIC 使用**连接 ID(Connection ID)**标识连接,与四元组解耦。网络切换时,客户端只需在新路径上发送携带相同 Connection ID 的包,连接即可无缝迁移,无需重新握手:

# 观察 QUIC 连接迁移
# 在 Chrome 中访问支持 HTTP/3 的站点
chrome://net-internals/#quic
# 切换网络(WiFi → 4G)后观察连接状态
# 连接 ID 不变,路径改变,连接继续

5.4 版本对比总览#

特性HTTP/1.1HTTP/2HTTP/3
传输层TCPTCPQUIC(UDP)
消息格式文本二进制帧二进制帧
多路复用无(管道化失败)流级多路复用流级多路复用
应用层队头阻塞
传输层队头阻塞有(单连接)有(所有流共享)无(流独立)
头部压缩HPACKQPACK
服务器推送有(已弃用)
连接建立1-RTT TCP1-RTT TCP + 1-RTT TLS1-RTT QUIC(含加密)
0-RTT不支持不支持支持
连接迁移不支持不支持支持
加密可选(HTTP 层无加密)可选(但实践中 TLS 是事实标准)必选(QUIC 内置加密)
Warning

HTTP/3 的 0-RTT 存在重放攻击风险——攻击者可以截获 0-RTT 数据并重放。0-RTT 请求必须是幂等的(如 GET 请求),不应包含非幂等操作(如支付请求)。服务器应实现 0-RTT 重放保护机制,如只允许特定路径使用 0-RTT。

六、HTTP 语义不变#

6.1 变的是传输,不变的是语义#

从 HTTP/1.0 到 HTTP/3,协议在传输层经历了翻天覆地的变化——文本变二进制、单流变多流、TCP 变 QUIC。但 HTTP 的语义层几乎没有变化:

  • 请求方法:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS——所有版本通用
  • 状态码:200、301、404、500——所有版本通用
  • 请求头与响应头Content-TypeCache-ControlAuthorization——所有版本通用
  • URI 格式https://example.com/path?query=value——所有版本通用

HTTP/2 和 HTTP/3 引入了**伪头部(pseudo-headers)**来替代 HTTP/1.x 请求行和状态行中的信息:

# HTTP/1.1 请求行
GET /index.html HTTP/1.1
Host: www.example.com
# HTTP/2、HTTP/3 伪头部
:method: GET
:path: /index.html
:scheme: https
:authority: www.example.com

伪头部以冒号开头,不是真正的头部字段,而是请求行/状态行的二进制编码形式。:authority 等价于 Host 头部,:method:path:scheme 对应请求行的各部分,:status 对应状态码。

6.2 为什么语义不变很重要#

语义不变意味着:应用层代码不需要因为 HTTP 版本升级而修改。你的 REST API、表单提交、文件上传——无论底层跑的是 HTTP/1.1、HTTP/2 还是 HTTP/3,行为完全一致。

变化的只是帧的传输方式——如何把请求和响应切成帧、如何在连接上复用、如何压缩头部。这些对应用层完全透明。浏览器和服务器在 TLS 握手阶段通过 ALPN(Application-Layer Protocol Negotiation)协商 HTTP 版本,应用层无需感知。

七、动手实践:观察 HTTP 版本差异#

7.1 用 curl 测试不同 HTTP 版本#

# HTTP/1.1(默认)
curl -I https://www.google.com
# 强制 HTTP/2
curl --http2 -I https://www.google.com
# 强制 HTTP/3(需要 curl 编译时启用 QUIC 支持)
curl --http3 -I https://www.google.com
# 查看协商过程(-v 显示详细握手信息)
curl -v --http2 https://www.google.com 2>&1 | grep -i "ALPN\|h2\|http/2"
# 对比不同版本的响应时间
time curl --http1.1 -so /dev/null https://www.google.com
time curl --http2 -so /dev/null https://www.google.com
time curl --http3 -so /dev/null https://www.google.com

7.2 Chrome DevTools 观察协议版本#

# 启用 Chrome HTTP/3 支持(Chrome 87+ 默认启用)
# 访问 chrome://flags/#enable-quic 确认已启用
# 在 DevTools 中查看协议版本:
# 1. 打开 DevTools(F12)
# 2. 切换到 Network 面板
# 3. 右键列标题 → 勾选 "Protocol" 列
# 4. 刷新页面,观察每个请求的协议版本(h2 或 h3)

Chrome DevTools 的 Network 面板中,Protocol 列显示每个请求使用的 HTTP 版本:

Protocol 列值含义
http/1.1HTTP/1.1
h2HTTP/2
h3HTTP/3 over QUIC
h3-29HTTP/3(QUIC 草案版本 29)

7.3 Wireshark 抓包分析#

# 抓取 HTTP/2 流量
sudo tshark -i eth0 -f "tcp port 443" -w http2_capture.pcap
# 抓取 HTTP/3(QUIC)流量
sudo tshark -i eth0 -f "udp port 443" -w http3_capture.pcap
# Wireshark 中分析 HTTP/2 帧
# 过滤器:http2
# 常用过滤器:
# http2.type == 1 # HEADERS 帧
# http2.type == 0 # DATA 帧
# http2.streamid == 1 # 特定流的帧
# http2.flags.ack == 1 # ACK 标志位
# 分析 QUIC 包
# 过滤器:quic
# 常用过滤器:
# quic.long.packet_type == 0 # Initial 包
# quic.long.packet_type == 2 # Handshake 包
# quic.dcil > 0 # 包含目标 Connection ID

7.4 nghttp2 工具集#

nghttp2 是 HTTP/2 和 HTTP/3 的命令行调试工具集:

# 安装 nghttp2 工具
sudo apt install nghttp2-client
# 发送 HTTP/2 请求并显示帧详情
nghttp -v https://www.google.com
# 显示 HPACK 压缩详情
nghttp -v --header-table-size=4096 https://www.google.com
# 发送多个并发请求观察多路复用
nghttp -v -m 10 https://www.google.com
# h2load:HTTP/2 压测工具
h2load -n 1000 -c 100 -m 10 https://www.example.com
# -n 1000:总共 1000 个请求
# -c 100:100 个并发连接
# -m 10:每个连接 10 个并发流

7.5 Nginx 配置 HTTP/2#

# Nginx 启用 HTTP/2
server {
listen 443 ssl http2; # HTTP/2
# listen 443 quic reuseport; # HTTP/3(Nginx 1.25+)
server_name www.example.com;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
# 通知浏览器支持 HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';
# HTTP/2 推送(可选,已不推荐)
# http2_push /style.css;
# http2_push /app.js;
location / {
proxy_pass http://backend;
}
}
# 验证 HTTP/2 是否生效
# curl -I --http2 https://www.example.com
# 响应头应包含:HTTP/2 200

7.6 Chrome QUIC 调试#

# 查看 Chrome 的 QUIC 会话信息
chrome://net-internals/#quic
# 启用 QUIC 日志
chrome://flags/#enable-quic
# 导出 NetLog 进行详细分析
chrome://net-export/
# 选择 "Include socket bytes" 获取完整的帧级数据
# 用 netlog_viewer 解析导出的 JSON 文件

八、本章小结#

HTTP 协议三十年的演进,核心驱动力始终是减少延迟提高连接利用率

版本解决的问题引入的机制遗留的问题
HTTP/1.0无元数据头部、状态码、Content-Type每请求新建连接
HTTP/1.1连接不复用持久连接、管道化、Host、分块编码应用层队头阻塞、头部冗余
HTTP/2应用层队头阻塞、头部冗余二进制帧、流多路复用、HPACK传输层队头阻塞(TCP 丢包影响所有流)
HTTP/3传输层队头阻塞、握手延迟QUIC 传输、QPACK、连接迁移、0-RTT0-RTT 重放风险、UDP 受限环境

每一代协议都解决了上一代的核心问题,同时也暴露出新的问题。HTTP/2 解决了应用层队头阻塞,却让传输层队头阻塞的影响范围扩大;HTTP/3 彻底解决了队头阻塞,却面临 UDP 在某些网络环境中被限速或封锁的挑战。

但贯穿所有版本的一个不变量是:HTTP 语义从未改变。方法、状态码、头部、URI——这些应用层开发者每天打交道的东西,在 HTTP/1.0 和 HTTP/3 中完全一致。变化的只是帧的传输方式,对应用层完全透明。

理解了 HTTP 协议的演进逻辑,下一章 CDN与内容分发 将讨论 HTTP 如何与 CDN 配合——当服务器不在用户附近时,CDN 如何通过边缘缓存、任播路由和回源策略,将内容”搬”到离用户更近的地方,进一步缩短延迟。


参考#

  • RFC 1945 — Hypertext Transfer Protocol — HTTP/1.0
  • RFC 2068 — Hypertext Transfer Protocol — HTTP/1.1
  • RFC 2616 — Hypertext Transfer Protocol — HTTP/1.1
  • RFC 7541 — HPACK: Header Compression for HTTP/2
  • RFC 9204 — QPACK: Header Compression for HTTP/3

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

HTTP协议演进:从1.0到3.0的性能革命
https://blog.souloss.com/posts/internet-architecture/http-protocol-evolution/
作者
Souloss
发布于
2022-08-22
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
QUIC与HTTP/3:传输层的性能革命
互联网运作 TCP的队头阻塞、握手延迟和连接迁移失败催生了QUIC——一个基于UDP的加密传输协议。0-RTT握手、流级多路复用、连接ID迁移,加上HTTP/3的QPACK头部压缩,传输层迎来了真正的性能革命。
2
系列导读
互联网运作 本系列从物理介质到应用层、从数据包离开网卡到抵达对端,用真实的包级追踪讲述互联网如何运作——物理编码、以太网交换、ARP首跳、IP寻址、NAT中间盒、域内域间路由、BGP安全、运营商骨干网、IXP互联、TCP/UDP/QUIC传输、DNS/TLS安全、HTTP演进、CDN分发、数据中心SDN、无线异构接入,每章配有Wireshark抓包与GNS3/FRR实验,让你从「会上网」进阶到「理解互联网」。
3
HTTP/1.0:扩展协议
web 在 HTTP/0.9 的基础上,探索 HTTP/1.0 引入的核心新特性——请求头与响应头、状态码、多种 HTTP 方法(GET/POST/HEAD),并动手实现一个支持 HTTP/1.0 的 Python 服务器。
4
HTTP/2:多路复用
web 探索 HTTP/2 如何突破 HTTP/1.1 的性能瓶颈——从 SPDY 的设计理念到二进制分帧层、多路复用、头部压缩(HPACK)、服务器推送与流优先级,用实验直观理解这些革命性改进。
5
HTTP/3:QUIC 传输
web 深入探索 HTTP/3 的革命性变化——基于 QUIC 协议的传输层如何解决 TCP 的队头阻塞问题,0-RTT 连接建立如何实现极速响应,连接迁移如何让网络切换无缝进行,以及 HTTP/3 与 HTTP/2 的关键差异。