模块一 · 应用层
HTTP/2 与 HTTP/3
HTTP/1.1可复用持久连接,但同连接响应排序和并发连接限制会影响多资源加载。HTTP/2 号称用"一条连接"解决,HTTP/3 更进一步换掉了传输层。本章用一个需要明确控制客户端配置的实验来证明多路复用,并且会诚实记录一次失败的实验。
本章要解决的问题:HTTP/1.1可复用持久连接,但同连接响应排序和并发连接限制会影响多资源加载。HTTP/2 号称用"一条连接"解决,HTTP/3 更进一步换掉了传输层。本章用一个需要明确控制客户端配置的实验来证明多路复用,并且会诚实记录一次失败的实验。
本章的证据来源:历史实测记录(本地 HTTP/1.1 与 HTTP/2 服务的连接数对比、Node 对真实网站的 ALPN 协商、curl 的能力检查)。协议定义出自 RFC 9113(HTTP/2)与 RFC 9114(HTTP/3)。
先从一个例子理解概念
一个页面要取图片、样式和脚本。怎样减少互相等待,又避免每个资源都重新建立通信?先认识复用、流和阻塞。
同一连接承载多段工作:持久连接与多路复用
持久连接允许结束一个请求后继续复用;多路复用让多个通信流交错传输。HTTP/1.1也能持久连接,不能把两者等同。
区分同连接里的工作:流与帧
HTTP/2用带标识的流区分请求/响应,并用帧组织传输。这种HTTP/2帧与以太网帧属于不同层,名字相同不代表格式相同。
前面没到,后面被卡住:队头阻塞
某个靠前内容缺失导致后续内容暂不能交付,叫队头阻塞。HTTP/2改善HTTP层的等待,但TCP字节流的有序交付限制仍存在。
不同流能分别恢复:QUIC
QUIC是基于UDP的传输协议,提供可靠流、安全和拥塞控制;HTTP/3把HTTP放在QUIC上。使用UDP不等于放弃可靠性。
换路径还能继续:连接迁移
连接迁移是在允许的条件下改换通信路径。QUIC可用连接标识与路径验证支持它,不是保证任何Wi-Fi切换都永不掉线。
下面再用这些概念看原理、命令和结果。规范与官方资料列在本章末尾,类比只帮助理解,不代替协议规则。
HTTP/1.1 的三个效率问题
问题一:连接数有限,且很贵。
一个网页通常有几十个资源(HTML、CSS、JS、图片、字体)。浏览器对同一个域名一般只开 6 条左右的并发连接。超出的请求要排队。
而每条新连接都要付出代价(这是前几个模块讲过的):
TCP 三次握手(1 个 RTT)
+ TLS 握手(1~2 个 RTT)
+ 慢启动(拥塞窗口从很小开始,前几个 RTT 传不了多少数据)问题二:队头阻塞(Head-of-Line Blocking)。
在一条 HTTP/1.1 连接上,请求必须排队:前一个响应没传完,后一个就不能开始。如果前一个请求的响应特别慢(比如一个慢查询),后面所有请求都被堵住。
问题三:头部重复且不压缩。
每个请求都要带上几乎相同的头部(User-Agent、Accept、Cookie……),几十个字节到几 KB。请求数一多,光头部就是不小的流量。
HTTP/2 的四项改进
| 改进 | 解决的问题 |
|---|---|
| 二进制分帧 | 不再用文本格式,改成二进制帧,解析更快、更规整 |
| 多路复用 | 一条连接上可以并发多个请求/响应(用"流"区分) |
| HPACK 头部压缩 | 头部用索引表压缩,重复的头字段只发一个索引号 |
| 流优先级 | 告诉服务器"这个资源更重要,先给我" |
其中"多路复用"是最核心的改进 —— 它同时解决了问题一和问题二。
实测:一次失败的实验(值得记录)
我的第一个想法是:用真实网站比较耗时。
思路很直接 —— 发 5 个请求,分别用 HTTP/1.1 和 HTTP/2,比总时间。
结果(历史实测记录):
HTTP/1.1 顺序 + 每请求新建连接 : 32910 ms
HTTP/2 单连接并发发送 : 34789 ms
HTTP/2 用时是 HTTP/1.1 的 106%HTTP/2 反而更慢了。 而且两个数字都不正常:5 个请求花了 33 秒,平均每个 6.6 秒。
为什么这个实验没有意义
历史耗时比较未充分控制连接复用、并发策略、缓存、服务端处理和代理路径,因此不能单独归因于协议版本。已知TUN存在,只能说它可能影响测量,不能把33秒都归给代理。
有效对照需固定资源、客户端能力、请求并发和复用策略,并记录每轮的连接、TLS协商、响应完成时间及重复测量分布。
实测:换一个不受延迟影响的指标
原本地演示把HTTP/1.1设为agent:false,五个请求主动建立五条连接;HTTP/2则明确共用一条连接并发五个流。
这证明了所设置的两种客户端行为,而不是HTTP/1.1必须五请求五连接。 HTTP/1.1默认可保持连接并顺序复用;HTTP/2的重要区别是同连接中交错多个流,不是简单“能用一条连接”。RFC 9112 §9.3、RFC 9113 §5
建议使用本地可控服务分别演示:HTTP/1.1单连接顺序复用、HTTP/1.1连接池并发、HTTP/2单连接多流并发。固定请求与响应,数连接同时看流ID及完成时序。
五次并发握手的墙钟耗时不能简单相加;只有按明确串行方式完成时,才可累加理想化RTT成本。原演示不支持“HTTP/2把2秒直接省到400ms”的通用结论。
实测:真实网站的协议协商
用 Node 的 TLS 模块对几个真实网站做 ALPN 协商(上一章的实测数据):
| 网站 | 协商结果 |
|---|---|
github.com | h2 |
www.cloudflare.com | h2 |
www.baidu.com | http/1.1 |
同一台机器、同一个客户端,访问不同网站可能用不同的协议版本。 这完全取决于服务器(和路径上的设备)支持什么,由 ALPN 协商决定。
这也解释了为什么 HTTP/2 的部署是"渐进"的 —— 它不是一次全网升级,而是每个网站自己决定什么时候开。
队头阻塞并没有被完全解决
这是本章最容易被误解的一点。
HTTP/2 解决的是 HTTP 层的队头阻塞(同一个连接上的多个请求不再互相排队)。
但 TCP 层的队头阻塞还在。 因为 HTTP/2 的所有"流"都跑在同一条 TCP 连接上,而 TCP 看到的只是字节流 —— 它不知道哪些字节属于哪个流。
于是:
HTTP/2 的一条 TCP 连接里,同时跑着流 A、流 B、流 C
某个 TCP 段丢了(里面有流 A 的数据)
→ TCP 必须等这个段重传成功,才能把后续数据交给应用层
→ 而后续数据里可能包含流 B、流 C 的内容
→ 所以【所有流都得等】,哪怕 B 和 C 自己的数据一点没丢这就是 TCP 层的队头阻塞。 一个丢包,整条连接上所有并发流一起卡住。
HTTP/3 = QUIC over UDP
HTTP/3 把传输层从 TCP 换成了 QUIC,而 QUIC 建立在 UDP 之上。
为什么用 UDP? 回顾《运输层》模块讲的:UDP 只有 8 字节头,什么都不保证,也不维护连接状态。
听起来是倒退,其实是绕开限制:
| 问题 | TCP 的限制 | QUIC 的做法 |
|---|---|---|
| 传输层队头阻塞 | TCP 不理解"流",一个丢包堵住全部 | QUIC 自己在协议里实现流,各流独立重传 |
| 建连慢 | TCP 握手 + TLS 握手分开做 | QUIC 把传输握手和 TLS 握手合并,1 个 RTT 就能发数据,恢复会话可 0-RTT |
| 换网络就断 | 普通TCP不能像QUIC一样通用地迁移四元组;MPTCP等扩展另论 | QUIC可用连接ID支持迁移,但需要协议许可、路径验证和实现支持,不保证任意换网都不断连 |
| 中间设备僵化 | 有些防火墙/中间设备会改写 TCP 头 | UDP 对中间设备更"透明",且 QUIC 强制加密,中间设备改不了 |
注意最后一条:QUIC采用分级的包保护。 Initial密钥可由公开信息推导,不提供对旁观者的机密性;Handshake和1RTT使用随后建立的秘密密钥,0RTT另有安全限制。不能把“握手加密”说成初始ClientHello也不可读取。RFC 9001 §5
实测限制:本章有哪些没能测到
诚实说明本机的限制条件:
curl --http2 https://www.baidu.com
→ curl: option --http2: the installed libcurl version does not support this本机的 curl 8.21.0 是用 Schannel 编译的,没有带 nghttp2(HTTP/2 的实现库)。所以上一章那个细节也解释通了 —— ALPN: curl offers http/1.1,它根本没能力提供 h2。
这是本次实验改用 Node 的原因 —— Node 自带完整的 HTTP/2 实现。
② 没能实测 HTTP/3。 本机的 curl 构建没有 HTTP/3 支持,也没有其他支持 QUIC 的命令行工具。所以本章关于 HTTP/3 的内容属于标准/原理,没有历史实测记录。
③ 本机 IPv6 不可用(《网络层》模块实测过 ping -6 直接 transmit failed),这也限制了 QUIC 部分特性(比如基于 IPv6 的连接迁移)的探索。
常见误解
误解一:"HTTP/2 一定比 HTTP/1.1 快"
不一定,要看场景。 在高延迟、多资源的场景下优势明显(省掉多次握手、支持并发)。但在低延迟、资源很少的场景下,差异可能很小,甚至因为 HTTP/2 的二进制帧和流管理开销而略慢。
误解二:"HTTP/2 解决了队头阻塞"
只解决了一半。 HTTP 层的队头阻塞解决了(多个请求可以并发),但 TCP 层的还在(一个 TCP 段丢失会让所有流一起等)。真正解决要靠 HTTP/3(QUIC)。
误解三:"HTTP/3 用 UDP,所以不可靠"
QUIC 在 UDP 之上自己实现了可靠传输(序号、确认、重传、拥塞控制 —— 《运输层》模块讲的那些,QUIC 都自己做了一遍)。它用 UDP 只是因为它需要"一个足够简单、中间设备不会乱改的底层通道",不代表它放弃可靠性。
误解四:"换个服务器配置就能用上 HTTP/2"
还要看客户端。 历史实测记录的 curl 就不支持 HTTP/2 —— 服务器再支持也没用。协议版本是双方协商的结果,任何一方不支持就退回旧版本。
本章小结
| 结论 | 类型 |
|---|---|
| HTTP/1.1 的三个效率问题:连接数限制、队头阻塞、头部不压缩 | 标准 |
| HTTP/2 的四项改进:二进制分帧、多路复用、HPACK、流优先级 | 标准 |
| HTTP/2 的服务器推送已被主流浏览器移除 | 标准 |
| 历史演示人为禁用HTTP/1.1复用;只能证明该客户端配置下的连接数 | 实验限制 |
| 实测:用真实网站比较耗时无法得出结论(HTTP/2 反而慢 6%) | 实测(结论无效) |
| 耗时与连接数比较均需控制复用、并发、缓存和路径,不能无对照归因代理 | 方法论 |
| 实测:github.com 与 cloudflare.com 协商为 h2,baidu.com 为 http/1.1 | 实测 |
| HTTP/2 只解决了 HTTP 层队头阻塞,TCP 层的仍然存在 | 标准 |
| HTTP/3 基于 QUIC(UDP),在协议内实现流,消除传输层队头阻塞 | 标准 |
| QUIC 合并传输与 TLS 握手,1-RTT 建连、恢复会话可 0-RTT | 标准 |
| QUIC支持有条件的连接迁移;路径失败或策略可阻止迁移 | RFC 9000 |
| 实测:本机 curl 构建不含 HTTP/2 支持,故改用 Node 做实验 | 实测 |
| 本章 HTTP/3 内容无历史实测记录,属标准/原理 | 实测限制 |
自测题
7 题 · 先自己答,再展开对照
HTTP/1.1 的三个效率问题分别是什么?
① 连接数有限且建立成本高(浏览器对同域名一般只开 6 条左右,每条都要付 TCP 握手 + TLS 握手 + 慢启动的成本);② 队头阻塞(同一条连接上的请求必须排队,前面的慢响应会堵住后面);③ 头部重复且不压缩(每个请求都带几乎相同的User-Agent、Cookie等,几十字节到几 KB)。HTTP/2 的多路复用解决了哪个问题?它是怎么在同一连接上区分不同请求的?
解决了"连接数限制"和"HTTP 层的队头阻塞"两个问题。
它把每个请求/响应拆成一个个二进制帧,每个帧上带有流 ID(Stream Identifier),标识它属于哪个请求。这样一条 TCP 连接上就能同时交错传输多个请求的帧,接收方按流 ID 重新组装,互不干扰。我第一次用真实网站比较耗时,得到 HTTP/2 更慢的结果。为什么这个实验没有意义?
缺少复用、并发、缓存、服务端与路径对照,不能把差异归因于协议或断言时间全由代理造成。应固定条件并重复测量。为什么"数连接数"比"比耗时"更适合验证多路复用?
连接数能检查客户端如何使用连接,但不是协议优劣的充分证明;agent:false强制新建不能代表HTTP/1.1能力,应同时演示持久连接和HTTP/2的多流交错。HTTP/2 为什么还有队头阻塞?这个阻塞发生在哪一层?
因为 HTTP/2 的多个"流"都跑在同一条 TCP 连接上,而 TCP 并不理解"流"的概念 —— 它看到的只是字节流,不知道哪些字节属于哪个流。
所以当某个 TCP 段丢失时,TCP 必须等它重传成功才能把后续数据交给应用层,而后续数据里可能包含其他流的内容 —— 于是所有流都被堵住,哪怕它们自己的数据一个字节都没丢。这个阻塞发生在传输层(TCP),不是 HTTP 层。HTTP/3 为什么选择基于 UDP?它不是放弃了可靠性吗?
用 UDP 是为了绕开 TCP 的结构性限制,不是放弃可靠性。 具体来说 TCP 有三个绕不开的问题:传输层队头阻塞(TCP 不理解流)、握手分两次做(TCP 握手 + TLS 握手)、连接由四元组标识(换 IP 就断)。
QUIC 在 UDP 之上自己实现了可靠传输(序号、确认、重传、拥塞控制,《运输层》模块讲的那套它都重新做了一遍),而且在协议内实现了流,所以各流可以独立重传。用 UDP 只是为了拿到"一个足够简单、中间设备不会乱改"的底层通道。你的服务器已经启用了 HTTP/2,但某个客户端还是用 HTTP/1.1 访问。可能是什么原因?
最可能的原因是客户端不支持 HTTP/2。 协议版本是 ALPN 协商的结果 —— 客户端在 TLS 握手的 ClientHello 里列出自己支持的协议,服务器从中选一个。如果客户端只提供http/1.1,服务器就只能用 HTTP/1.1。
本机就有现成的例子:curl 8.21.0(Schannel 构建)没有编译进 HTTP/2 支持,它的 ALPN 只提供http/1.1,所以即使服务器支持 h2 也用不上。其他可能:中间设备(代理、防火墙)改写了 ALPN 协商,或者客户端与服务器之间必须经过只支持 HTTP/1.1 的代理。
参考资料与适用范围
- RFC 9112 §6–9:HTTP/1.1消息与持久连接
- RFC 9113 §5–6:HTTP/2流与帧
- RFC 9001 §5:QUIC各级包保护,Initial不提供秘密机密性
- RFC 9000 §9:QUIC路径验证与连接迁移
- RFC 8446 §2.3、§4.4、§7–8:TLS1.3认证、密钥与早期数据限制
- Chrome官方公告:106版本禁用HTTP/2 Server Push
修订核对日期:2026-10-13。规则按所列协议与实现范围理解;文中的原始命令输出是历史观察,不表示本次重新实测。跨版本、代理/隧道及不同捕获位置需要另行核对。