HTTP / HTTP/2 / HTTP/3
浏览器用一条连接获取页面文件时,HTTP 版本决定请求如何共享连接,以及丢包后谁要等待。本课的 HTTP/1.1 按顺序发送请求;HTTP/2 在一个 TCP 字节流中复用多个流;HTTP/3 在 QUIC 中按流交付数据。
同一个四文件页面会展示三种结果:HTTP/1.1 的请求排成阶梯;HTTP/2 的请求重叠,但 CSS 所在的 TCP 包丢失后,已经收到的 JavaScript 和图片也要等待;HTTP/3 遇到同样的丢包,JavaScript 和图片先完成,CSS 等待补传。它们分别展示请求排队、TCP 队头阻塞(HOL)和按流交付。
各版本如何共享连接
比较固定为一个源站、一条连接。浏览器常为 HTTP/1.1 的同一源站打开约六条连接,具体限制取决于实现;本课先观察单条连接。
这里的 HTTP/1.1 使用 keep-alive,不使用流水线,in_flight 最多为 1。GET /style.css 必须收到完整的 200 响应,才发送 /app.js。丢包拖慢的是队列,因为其他请求还没发出。HTTP/1.1 协议也定义了流水线:可先发送多个请求,但响应必须按顺序返回。
HTTP/2 建立一个 TCP 连接和 TLS 会话,通过 ALPN 协商 h2。客户端发起的流使用奇数 ID:/ 是 1,其他文件是 3、5、7。各流的帧交错发送,但共享 TCP 序号空间。瀑布图中的文件条可以重叠。TCP 必须按序向应用交付字节;CSS 数据留下一个缺口后,后续 JavaScript 和图片即使已在接收缓冲区,也不能交给 HTTP/2。
HTTP/3 使用基于 UDP 的 QUIC,TLS 1.3 集成在传输协议中。本例的新连接建立用一个 RTT,省去 TCP 与 TLS 分开的两个 RTT。数据按流复用,交付顺序由 STREAM ID 和偏移决定;丢包检测与拥塞控制仍由连接管理。QUIC 包号不是每个流独有,而是分为 Initial、Handshake、Application Data 三个包号空间,0-RTT 与 1-RTT 共用最后一个。只包含 CSS 流 4 数据的包丢失时,流 8、12 仍能交付。
前端代码不能用一个 fetch 参数选择 HTTP/3。源站或 CDN 通过 Alt-Svc 或 HTTPS DNS 记录公布支持情况,浏览器在握手中协商 h3。Chrome DevTools → Network 可启用 Protocol 列,查看 h3、h2、http/1.1,再对照 Timing 和瀑布图。丢包路径更能体现按流交付的作用;本课的干净路径主要体现建立连接的 RTT 差异,不能据此保证实际加速。
HTTP 版本逐步演示
默认 Compare 使用 /、/style.css、/app.js、/photo.jpg 四个文件和一条连接。先展示建立连接,再比较 HTTP/1.1 无丢包、HTTP/2 丢 CSS 包、HTTP/3 丢同样的包。
- 初始化。 列出四个文件,只有
/待请求。状态标出排队、阻塞、隔离三种行为。 - TCP。 三次握手合并为一个 RTT,连接行增加 TCP 区块。握手细节见 TCP Handshake 课。
- TLS 1.3。 再用一个 RTT,细节见 TLS Handshake 课。此时尚未发送 HTTP,连接建立已占两列。
- GET /。收到
200,rtt = 3,done = 1/4。 - 解析 HTML。 发现另外三个文件,才把它们加入待请求列表。
- GET /style.css。连接忙,JavaScript 和图片排队。
- GET /app.js。CSS 完成,图片仍排队。
- GET /photo.jpg。最后一个文件开始。瀑布图呈阶梯,文件条没有重叠。
- 切到 HTTP/2。 同一页面的附加文件并发传输,设定丢失一个只承载 CSS 流数据的传输包。
- 打开流 3、5、7。 三个 HEADERS 经同一个 TCP 连接发送。
- DATA。 css、js、img 同占一列,展示多路复用。
- 丢包。 CSS 包丢失,JavaScript 和图片已收到,留在缓冲区。
- TCP 队头阻塞。 三个附加文件都停住,后续 TCP 字节等待缺口补齐。
- 切到 HTTP/3。 改用 QUIC 流,使用同样的丢包条件。
- 再次发送 DATA。 三个帧通过 QUIC 流 4、8、12 发送。
- 丢包。 CSS 缺失,JavaScript 和图片仍可交付。
- 按流隔离。 JavaScript 和图片完成,CSS 未完成。CSS 的丢失数据会放进新包补传,其他流无需等它。
- 结束。 对比排队、TCP 阻塞、按流交付。源站或 CDN 启用 HTTP/3 后,可在开发者工具的 Protocol 列确认
h3。
单版本页签重放各自过程。Clean:HTTP/1.1 在 rtt = 6 完成,HTTP/2 为 4,HTTP/3 为 3。Lost packet:HTTP/1.1 为 8,丢包时 JavaScript 还在队列;HTTP/2 为 6,只补传缺失的 TCP 数据,随后各流解除阻塞;HTTP/3 为 5,只有 CSS 等待补传。
画布中的管道展示当前 RTT 的帧,瀑布图累计所有 RTT,章节刻度区分 H1 / H2 / H3。观察文件条是否重叠,以及 CSS 的红色缺口是否拖住其他文件。
一个丢包为何影响三个文件
HTTP/2 之前,浏览器已用多条 HTTP/1.1 连接实现并发。HTTP/2 把并发放到一条连接中,共用一次 TLS 握手和一个拥塞窗口。第七个样式表无需等一条 HTTP/1.1 socket 空闲,但仍受流数量和流量控制限制。
HTTP/2 没有改变 TCP 的按序字节流契约。多个 HTTP 流共享该字节流,交付就会受同一个缺口影响。QUIC 保留多路复用,把有序交付的范围缩到各流。
本例无丢包的 RTT 账目是 6、4、3:
- HTTP/1.1:建立连接 2 + HTML 1 + 三个附加文件 3。
- HTTP/2:建立连接 2 + HTML 1 + 并发附加文件 1。
- HTTP/3:建立连接 1 + HTML 1 + 并发附加文件 1。
丢包增加两列,用于丢失与重试;HTTP/3 的 JavaScript 和图片仍在第一个附加文件 RTT 完成。这是固定模型的计数,不是性能基准或真实 RTO 测量。
边界情况
- 解析后才发现附加文件。 本课不会提前请求
/style.css。真实浏览器可以用预加载扫描器等方式让解析与请求重叠。 - HTTP/1.1 的等待是排队。 丢包后即使管道空着,下一个 GET 也可能尚未发送。
- HTTP/2 的等待是 TCP 缺口。 后续帧已到达却无法交付,应称为“已缓冲”,不能说它们也丢了。
- HTTP/3 只隔离相关流的交付。 连接关闭仍会终止所有流。若丢失的包同时承载 CSS 和 JavaScript 的 STREAM 帧,两者都会等待各自缺失的数据。共享拥塞控制、连接流量控制和 QPACK 依赖也可能影响其他流。
- 流 ID 规则不同。 HTTP/2 客户端发起的流为 1、3、5、7,偶数留给服务端。QUIC 流 ID 的低两位编码发起方与方向,HTTP/3 客户端双向流为 0、4、8、12。
常见错误
- 把六条 HTTP/1.1 连接当成单连接多路复用。 本例每条连接一次处理一个请求。HTTP/1.1 流水线仍要求响应有序;HTTP/2 可交错发送响应,但并发数并非无限。
- 把 HTTP/2 复用叫作 HTTP/1.1 流水线。 流水线发送多个请求后仍按顺序收响应,浏览器通常不启用。HTTP/2 的帧可以交错,却仍受 TCP 丢包阻塞。
- 断言 HTTP/3 总是更快。 本例的新连接少一个 RTT;真实结果还取决于恢复会话、网络条件等因素。
- 给每个 QUIC 流分配独立包号。 包号属于连接的三个包号空间;按流交付依靠 STREAM ID 与偏移。
- 把本课当成 HTTPS 入门。 三种演示都加密:HTTP/1.1、HTTP/2 使用 TLS,HTTP/3 使用 QUIC 集成的 TLS。密钥如何产生见 TLS Handshake。
- 只记状态码。
200只是成功响应。本课关注谁能同时发送,以及丢包时谁要等。
模型的简化
真实浏览器可能为 HTTP/1.1 打开约六条源站连接,并让预加载、解析和请求重叠。HPACK、QPACK 压缩首部;主流浏览器已移除 HTTP/2 服务端推送实现,但协议仍保留该能力;优先级支持因实现而异。HTTP/3 的 0-RTT 早期数据还涉及会话恢复,本课没有模拟。
这里的丢包只影响一个承载 CSS 数据的传输包。TCP、TLS 以分组步骤展示,RTT 数只用于解释四文件模型。规范见 RFC 9113(HTTP/2)、RFC 9114(HTTP/3)、RFC 9000 / 9002(QUIC)。