前面几篇实验,我们看到了 HTTP/2 如何用多路复用解决应用层的队头阻塞。但 HTTP/2 还有一个根本问题:它跑在 TCP 之上。TCP 本身也有队头阻塞,丢一个包,整条连接上的所有流都得等重传。HTTP/2 的多路复用只解决了应用层的排队问题,传输层的排队问题还在。
然而,HTTP/2 虽然在应用层实现了多路复用,但它仍然运行在 TCP 之上。TCP 作为传输层协议,有一个根本性的问题:队头阻塞(Head-of-Line Blocking)。当一个 TCP 数据包丢失时,即使后续数据包已经到达,TCP 也必须等待重传后才能继续交付数据。这对于现代网页(包含数十个资源请求)来说是灾难性的。
更糟糕的是,TCP 连接建立的延迟在现代 Web 中已难以忍受:
- TCP 三次握手:1.5 RTT(客户端发送 SYN → 服务器回复 SYN-ACK → 客户端发送 ACK)
- TLS 握手:1-2 RTT(取决于 TLS 版本和会话复用)
- 总计:2.5-3.5 RTT 才能发送第一个 HTTP 请求
这意味着在高延迟网络(如移动网络)中,用户可能需要等待数百毫秒甚至数秒才能真正开始加载页面内容。
2012 年,Google 开始实验一个名为 SPDY 的协议,后来演变为 HTTP/2。同期,Google 也在研发一个全新的传输协议:QUIC(Quick UDP Internet Connections)。QUIC 的目标很明确:把 TCP 的可靠传输和 TLS 的安全性整合到 UDP 之上,彻底解决 TCP 的队头阻塞问题,同时大幅降低连接建立延迟。
2022 年,HTTP/3 作为 RFC 9114 正式发布,成为 HTTP 协议家族的最新成员。它最大的变化是:不再使用 TCP,而是运行在 QUIC 协议之上。
一、QUIC 协议详解
1.1 QUIC vs TCP:架构对比
关键区别:
QUIC 内置加密:TLS 1.3 直接集成到 QUIC 握手中,所有 QUIC 数据包(包括握手包)都是加密的。这解决了 TCP + TLS 的「明文握手」问题。
QUIC 在 UDP 之上实现可靠性:QUIC 自己实现了丢包重传、拥塞控制、流量控制等机制。这意味着 QUIC 可以在不修改操作系统内核的情况下快速迭代。
QUIC 原生支持多路复用:QUIC 的「流」(Stream)概念直接支持多个独立的数据流,丢失一个流的数据包不会影响其他流。
1.2 连接建立:QUIC 如何实现 0-RTT
TCP + TLS 1.2 的连接建立需要 2-3 个 RTT。QUIC 通过内置 TLS 1.3,可以实现惊人的 0-RTT 连接恢复:
0-RTT 的原理是:客户端在首次连接成功后,服务器会返回一个「会话票据」(Session Ticket)。客户端保存这个票据,下次连接时可以立即发送加密数据,无需等待握手完成。
0-RTT 数据有「重放攻击」风险,不适合非幂等请求(如支付、状态变更)。QUIC 协议本身不防止 0-RTT 重放,应用层需要自行处理。
1.3 解决队头阻塞:QUIC 的多流机制
TCP 的队头阻塞问题可以用下图说明:
TCP 层面的队头阻塞:
QUIC 的独立流机制:
QUIC 的每个流都有独立的序列号空间,丢失一个流的数据包不会影响其他流的数据交付。这对于现代网页加载多个资源(HTML、CSS、JS、图片等)来说意义重大。
1.4 连接迁移:网络切换不中断
TCP 连接由四元组标识:(源 IP、源端口、目的 IP、目的端口)。当用户从 WiFi 切换到移动网络时,IP 地址改变,TCP 连接就会断开。
QUIC 使用 Connection ID 来标识连接,而不是依赖四元组:
TCP 连接切换:
QUIC 连接迁移:
这对于移动用户来说是巨大的体验提升。当你在地铁里从 WiFi 切换到移动网络时,正在进行的下载、视频通话不会中断。
二、HTTP/3 的具体变化
2.1 从 HTTP/2 到 HTTP/3:帧格式变化
HTTP/3 仍然使用「帧」(Frame)的概念,但帧格式有所调整以适应 QUIC:
HTTP/2 帧格式(在 TCP 流上):
| 字段 | 长度 | 说明 |
|---|---|---|
| Length | 24 bits | 帧负载长度 |
| Type | 8 bits | 帧类型 |
| Flags | 8 bits | 标志位 |
| R | 1 bit | 保留位 |
| Stream ID | 31 bits | 流标识符 |
| Frame Payload | 可变 | 帧负载 |
HTTP/3 帧格式(在 QUIC 流上):
| 字段 | 长度 | 说明 |
|---|---|---|
| Type | varint | 帧类型 |
| Length | varint | 帧负载长度 |
| Frame Payload | 可变 | 帧负载 |
主要变化:
帧头更简洁:HTTP/3 使用可变长度整数(varint),不再需要 24 位固定长度字段。移除了 Flags 和保留位,因为 QUIC 流本身已经处理了这些功能。
Stream ID 由 QUIC 管理:HTTP/2 的 Stream ID 在帧头中,HTTP/3 的流标识由 QUIC 层管理,不再出现在帧头中。
帧类型调整:
| HTTP/2 帧类型 | HTTP/3 对应 | 说明 |
|---|---|---|
| HEADERS | HEADERS | 压缩的头部块 |
| PRIORITY | (移除) | 由 QUIC 流优先级替代 |
| RST_STREAM | (移除) | 由 QUIC 的 STOP_SENDING 替代 |
| SETTINGS | SETTINGS | 连接参数 |
| PUSH_PROMISE | (移除) | HTTP/3 不支持服务器推送 |
| PING | (移除) | 由 QUIC 的 PING 替代 |
| GOAWAY | GOAWAY | 连接关闭通知 |
| WINDOW_UPDATE | (移除) | 由 QUIC 流量控制替代 |
| CONTINUATION | (移除) | 不再需要,QUIC 流独立 |
2.2 QPACK:HTTP/3 的头部压缩
HTTP/2 使用 HPACK 进行头部压缩,依赖传输层的顺序性。由于 QUIC 的多流是独立的,HTTP/3 引入了 QPACK(QPACK Header Compression for HTTP):
HPACK (HTTP/2) 依赖动态表的顺序更新,如果 Stream 3 的 HEADERS 帧丢失,后续流就无法解码。
QPACK (HTTP/3) 的解决方案:
QPACK 通过引入专门的编码器流和解码器流,实现了与 HPACK 相似的压缩效率,同时支持乱序处理。
2.3 流量控制与优先级
HTTP/2 在应用层实现了流量控制和优先级,HTTP/3 则将这部分责任下放给 QUIC:
HTTP/2 的流量控制:
- 通过 WINDOW_UPDATE 帧管理
- 连接级别和流级别两个层次
- 应用层实现,与 TCP 流量控制可能冲突
HTTP/3 的流量控制:
- QUIC 层原生支持
- 连接级别(所有流总和)和流级别
- 自动流量控制帧(无需应用层处理)
- 与 QUIC 的拥塞控制协同工作
优先级方面,HTTP/2 的优先级机制相当复杂,实际实现效果参差不齐。HTTP/3 简化了这一设计,鼓励使用更简单的优先级模型。
HTTP/2 在应用层实现流量控制,和 TCP 的流量控制可能冲突。一个常见问题:HTTP/2 的 WINDOW_UPDATE 帧和 TCP 的接收窗口独立运作,可能导致一方放行而另一方阻塞。QUIC 把流量控制收归传输层,和拥塞控制协同工作,避免了这种冲突。优先级方面,HTTP/2 的树形优先级模型太复杂,Chrome 实际简化为 5 级权重。HTTP/3 进一步简化,RFC 9218 定义了 Extensible Prioritization Scheme,鼓励使用更简单的优先级模型。
2.4 不支持服务器推送
HTTP/2 引入了服务器推送,允许服务器在客户端请求 index.html 时主动推送 style.css 和 main.js。初衷是减少往返:服务器知道 HTML 需要哪些资源,不用等客户端解析 HTML 后再来请求。但实际使用中问题不少。
推送命中率低。浏览器可能已经缓存了这些资源,服务器不知道,推了等于白推,浪费带宽。
推送缓存管理复杂。浏览器需要维护推送的缓存,和常规缓存协调。Chrome 的实现曾出现缓存一致性 bug,导致推送的资源被重复请求。
与预加载策略冲突。<link rel="preload"> 和 103 Early Hints 是客户端驱动的预加载,和服务器推送的服务端驱动逻辑冲突,浏览器不知道该听谁的。
HTTP/3 移除了服务器推送,取而代之的是 103 Early Hints 状态码。服务器在发送最终响应前,先发一个 103 响应告诉客户端”你接下来会需要这些资源”,客户端自己决定是否请求。从”推数据”变为”推提示”,客户端重新掌握了主动权。
三、实验一:观察 HTTP/3 连接
QUIC 运行在 UDP 上,无法像前几篇那样用 nc 手动发送请求。但可以用 Caddy 搭建本地 HTTP/3 服务器,再用 curl 直连观察 QUIC 帧的交互。
3.1 搭建本地 HTTP/3 服务器
Caddy 从 v2.6 起默认启用 HTTP/3,是最快的实验方式。
# 安装 Caddy(Ubuntu/Debian)sudo apt install -y caddy
# 创建测试文件mkdir -p /tmp/caddy-wwwcat > /tmp/caddy-www/index.html << 'HTMLEOF'<!DOCTYPE html><html><head><title>HTTP/3 Test</title></head><body><h1>HTTP/3 Test Server</h1><p>Hello from HTTP/3!</p></body></html>HTMLEOF
cat > /tmp/caddy-www/data.json << 'JSONEOF'{"protocol": "HTTP/3", "status": "ok", "server": "Caddy"}JSONEOF
# 创建配置文件cat > /tmp/Caddyfile << 'CADDYEOF'localhost:8443 { root * /tmp/caddy-www file_server tls internal}CADDYEOF
# 启动(前台运行,另开终端测试)caddy run --config /tmp/CaddyfileCaddy 会在 localhost:8443 上同时监听 TCP(HTTP/1.1、HTTP/2)和 UDP(HTTP/3)。
3.2 使用 curl 测试 HTTP/3
大多数 Linux 发行版的默认 curl 不含 HTTP/3 支持。需要编译带 QUIC 的版本:
# 安装依赖sudo apt install -y libngtcp2-dev libngtcp2-crypto-gnutls-dev libnghttp3-dev libgnutls28-dev
# 编译 curlCURL_VER=8.13.0curl -sL "https://curl.se/download/curl-${CURL_VER}.tar.gz" -o curl.tar.gztar xzf curl.tar.gz && cd curl-${CURL_VER}./configure --with-gnutls --with-ngtcp2 --with-nghttp3make -j$(nproc) && sudo make install确认 HTTP/3 可用后,发起请求:
# 确认 HTTP/3 支持/usr/local/bin/curl --version | grep HTTP3
# 发起 HTTP/3 请求curl --http3 -k https://localhost:8443/输出:
<!DOCTYPE html><html><head><title>HTTP/3 Test</title></head><body><h1>HTTP/3 Test Server</h1><p>Hello from HTTP/3!</p></body></html>用 -v 观察连接详情:
curl --http3 -v -k https://localhost:8443/输出:
* Connected to localhost (::1) port 8443* using HTTP/3* [HTTP/3] [0] OPENED stream for https://localhost:8443/* [HTTP/3] [0] [:method: GET]* [HTTP/3] [0] [:scheme: https]* [HTTP/3] [0] [:authority: localhost:8443]* [HTTP/3] [0] [:path: /]> GET / HTTP/3> Host: localhost:8443>< HTTP/3 200< content-type: text/html; charset=utf-8< content-length: 166< server: Caddy关键标识:using HTTP/3、GET / HTTP/3、HTTP/3 200。
3.3 比较三种协议的连接时间
用 curl 的 -w 选项量化三种协议的差异:
# HTTP/1.1curl --http1.1 -k -s -o /dev/null \ -w "Version: %{http_version}\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \ https://localhost:8443/
# HTTP/2curl --http2 -k -s -o /dev/null \ -w "Version: %{http_version}\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \ https://localhost:8443/
# HTTP/3curl --http3 -k -s -o /dev/null \ -w "Version: %{http_version}\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \ https://localhost:8443/本地 localhost 测试结果:
--- HTTP/1.1 ---Version: 1.1Connect: 0.000470sTTFB: 0.002408sTotal: 0.002441s
--- HTTP/2 ---Version: 2Connect: 0.000293sTTFB: 0.002360sTotal: 0.002419s
--- HTTP/3 ---Version: 3Connect: 0.002043sTTFB: 0.003342sTotal: 0.003439s本地测试中 HTTP/3 反而最慢,因为 QUIC 握手的加密计算开销比 TCP + TLS 更大。HTTP/3 的优势在高延迟网络(跨国访问、移动网络)和后续连接的 0-RTT 场景中才体现。
3.4 对比首次连接和二次连接
0-RTT 的优势体现在二次连接上。先做一次请求让 curl 保存会话票据,再用保存的票据做第二次请求:
# 首次连接(1-RTT)curl --http3 -k -s -o /dev/null \ -w "Version: %{http_version}\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \ https://localhost:8443/
# 保存会话数据curl --http3 -k -c /tmp/quic-cookies.txt https://localhost:8443/ > /dev/null
# 二次连接(可能 0-RTT)curl --http3 -k -b /tmp/quic-cookies.txt -s -o /dev/null \ -w "Version: %{http_version}\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \ https://localhost:8443/注意观察二次连接的 TTFB 是否显著降低。如果本地测试差异不大(因为 localhost 延迟太低),可以想象在高延迟网络(100ms+ RTT)中,省掉一个 RTT 意味着首字节延迟减少 100ms 以上。
0-RTT 数据有重放攻击风险,不适合非幂等请求(如支付、状态变更)。QUIC 协议本身不防止 0-RTT 重放,应用层需要自行处理。对于 GET 请求这种幂等操作,0-RTT 是安全的。
四、实验二:用 Python 发起 HTTP/3 请求
Python 标准库和主流 HTTP 库(如 httpx、requests)截至 2025 年尚不支持 HTTP/3。目前可用的是 aioquic,这是一个纯 Python 实现的 QUIC 和 HTTP/3 库。如果偏好命令行工具,建议使用上文的 curl 方案。
安装依赖:
pip install aioquicHTTP/3 客户端示例(连接上文搭建的本地 Caddy 服务器):
#!/usr/bin/env python3# http3_client.py -- HTTP/3 client demo using aioquicimport asynciofrom aioquic.asyncio.client import connectfrom aioquic.h3.connection import H3Connectionfrom aioquic.h3.events import HeadersReceived, DataReceivedfrom aioquic.quic.configuration import QuicConfiguration
async def http3_get(url: str): """ 发起 HTTP/3 GET 请求 需要: pip install aioquic """ from urllib.parse import urlparse
parsed = urlparse(url) host = parsed.hostname port = parsed.port or 443 path = parsed.path or "/"
# 创建 QUIC 配置 configuration = QuicConfiguration()
# 连接本地自签名服务器时需要跳过证书验证 configuration.verify_mode = 0
async with connect(host, port, configuration=configuration) as protocol: # 创建 HTTP/3 连接 h3 = H3Connection(protocol._quic)
# 获取下一个可用的流 ID stream_id = protocol._quic.get_next_available_stream_id()
# 发送请求头 h3.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", f"{host}:{port}".encode()), (b":path", path.encode()), (b"user-agent", b"aioquic-h3-demo/1.0"), ], ) # 发送空请求体,结束流 h3.send_data(stream_id=stream_id, data=b"", end_stream=True) protocol.transmit()
# 接收响应 response_headers = {} response_body = b""
# 通过 QUIC 事件驱动接收 H3 事件 while True: event = protocol._quic.next_event() if event is None: await asyncio.sleep(0.01) continue for h3_event in h3.handle_event(event): if isinstance(h3_event, HeadersReceived): for name, value in h3_event.headers: response_headers[name.decode()] = value.decode() elif isinstance(h3_event, DataReceived): response_body += h3_event.data if h3_event.stream_ended: return response_headers, response_body
if __name__ == "__main__": headers, body = asyncio.run(http3_get("https://localhost:8443/")) print(f"Status: {headers.get(':status', 'unknown')}") print(f"Content-Type: {headers.get('content-type', 'unknown')}") print(f"Body length: {len(body)} bytes") print(f"Body: {body.decode()[:100]}...")五、实验三:观察 QUIC 数据包
使用 tcpdump 捕获本地 Caddy 的 QUIC 流量,可以直观看到 QUIC 运行在 UDP 之上:
# 在一个终端捕获 QUIC 流量(Caddy 监听在 8443 端口)sudo tcpdump -i lo -n udp port 8443 -w quic_capture.pcap
# 在另一个终端发起 HTTP/3 请求curl --http3 -k https://localhost:8443/
# 停止 tcpdump 后用 Wireshark 分析wireshark quic_capture.pcap在 Wireshark 中你会看到:
No. Time Source Dest Protocol Info1 0.000 ::1 ::1 UDP 54321 → 8443 Len=1250 (QUIC Initial)2 0.025 ::1 ::1 UDP 8443 → 54321 Len=1250 (QUIC Initial)3 0.026 ::1 ::1 UDP 8443 → 54321 Len=1250 (QUIC Handshake)4 0.051 ::1 ::1 UDP 54321 → 8443 Len=100 (QUIC Handshake)5 0.075 ::1 ::1 UDP 8443 → 54321 Len=1250 (QUIC 1-RTT Protected)关键观察:所有 QUIC 数据包都是 UDP 协议,不是 TCP;Initial 包虽然加密,但使用公开密钥,Wireshark 可以识别为 QUIC Initial;1-RTT Protected 包使用会话密钥加密,完全不可解密(除非有会话密钥);握手流程清晰可见:Initial → Handshake → 1-RTT Protected。
六、浏览器与服务器支持现状
6.1 浏览器支持
| 浏览器 | HTTP/3 支持 | 默认启用 | 说明 |
|---|---|---|---|
| Firefox 88+ | 是 | 是 | 稳定支持 |
| Safari 16+ | 是 | 是 | macOS Ventura、iOS 16+ |
| Edge 87+ | 是 | 是 | 基于 Chromium |
| Opera 73+ | 是 | 是 | 基于 Chromium |
浏览器发现 HTTP/3 支持的方式:
- Alt-Svc 头:服务器返回
Alt-Svc: h3=":443"; ma=2592000,告诉客户端支持 HTTP/3 - HTTP/3 专用端口:客户端下次连接时尝试 HTTP/3
6.2 服务器支持
主流 Web 服务器和 CDN 的支持情况:
| 服务器 | HTTP/3 支持说明 |
|---|---|
| Nginx | 1.25+ 主线版本支持,需编译时启用 --with-http_v3_module |
| Apache | 2.4.58+ 可通过 mod_http3 实验性模块支持,需单独编译安装 |
| Caddy | v2.6+ 默认启用 HTTP/3 |
| Traefik | v2.5+ 支持 HTTP/3 |
| Cloudflare | 全面支持 HTTP/3 |
| AWS ALB | 支持 HTTP/3 |
| GCP LB | 支持 HTTP/3 |
| Fastly | 支持 HTTP/3 |
Nginx 配置示例:
# Nginx 1.25.1+ 配置 HTTP/3server { listen 443 quic reuseport; # QUIC 监听(启用 HTTP/3) listen 443 ssl; # TCP 监听(HTTP/1.1/2 回退) server_name example.com;
ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem;
# Nginx 1.25.1+ 可用 http3 指令显式启用 http3 on;
# Alt-Svc 头,告诉浏览器支持 HTTP/3 add_header Alt-Svc 'h3=":443"; ma=86400';
location / { root /var/www/html; }}Caddy 配置(更简单):
# Caddy 默认启用 HTTP/3,无需额外配置example.com { root * /var/www/html file_server}七、HTTP 版本对比总览
| 特性 | HTTP/1.0 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|---|
| 传输层协议 | TCP | TCP | TCP | QUIC (UDP) |
| 连接复用 | 每次新建 | Keep-Alive | 多路复用 | 多路复用 |
| 队头阻塞 | 严重(连接级) | 严重(连接级) | 存在(TCP 级) | 已解决 |
| 头部压缩 | 无 | 无 | HPACK | QPACK |
| 加密 | 可选 | 可选 | 可选 | 强制(TLS 1.3) |
| 连接建立延迟 | 1-3 RTT | 1-3 RTT | 1-3 RTT | 0-1 RTT |
| 服务器推送 | 不支持 | 不支持 | 支持 | 移除 |
| 连接迁移 | 不支持 | 不支持 | 不支持 | 支持 |
| 请求方法 | GET/POST/HEAD | 全部方法 | 全部方法 | 全部方法 |
| 二进制协议 | 文本 | 文本 | 二进制帧 | 二进制帧 |
| 发布年份 | 1996 | 1997/1999 | 2015 | 2022 |
| RFC 编号 | RFC 1945 | RFC 2616/7230 | RFC 7540/9113 | RFC 9114 |
八、何时选择 HTTP/3
HTTP/3 并非万能药。在移动网络环境(WiFi 与蜂窝网络频繁切换)、高延迟网络(跨国访问、卫星网络)、大量并发请求的页面(SPA、资源密集型网站)和实时通信应用(视频会议、在线游戏)中,HTTP/3 的连接迁移和多路复用优势明显。
在网络稳定、延迟低的场景下,HTTP/2 仍然够用。如果服务器不支持 QUIC 或客户端环境受限,强行启用 HTTP/3 反而增加复杂度。
UDP 在某些网络环境下可能被限速或屏蔽,比如部分企业防火墙只放行 TCP 80/443。QUIC 的加密也有额外的 CPU 开销,首次连接的加密计算比 TCP + TLS 更重。另外,0-RTT 的重放风险需要应用层防范。这些因素都需要在选型时权衡。
九、观察总结
HTTP/3 从 TCP 迁移到 QUIC,是 HTTP 协议在传输层的一次根本性架构变更。QUIC 把 TCP 的可靠传输和 TLS 的安全性整合到 UDP 之上,用 Connection ID 解决连接迁移,用独立流解决队头阻塞,用 0-RTT 缩短重连延迟。代价是首次连接的加密计算开销更大,0-RTT 的重放风险需要应用层防范,UDP 在部分网络环境可能受限。
帧格式从 HTTP/2 的 9 字节帧头简化为 varint 编码,QPACK 替代 HPACK 适配 QUIC 的乱序特性,服务器推送被移除。这些变化都指向同一个设计意图:让 HTTP 的工作方式匹配 QUIC 传输层的特性,而不是在 QUIC 上硬套 HTTP/2 的设计。
参考资料
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






