技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTNET

十分钟讲完 QUIC 协议,你懂了吗?

所属馆藏
NET Protocol
文件格式
Markdown
原始路径
NET Protocol/21-十分钟讲完 QUIC 协议,你懂了吗?
本文目录7 个章节
  1. 一、从 HTTP/1.1 到 HTTP/2
  2. 二、QUIC 协议
  3. 三、QUIC 的其他特性
  4. 四、QUIC 相比 HTTP/2 的主要优势
  5. 五、QUIC 相关资料与实现
  6. 六、总结
  7. 参考资料

十分钟讲完 QUIC 协议,你懂了吗?

Category(分类): NET Protocol Status: 未知

本文尽量保留原文的叙述顺序和历史背景,并在相关位置补充 HTTP/2、TLS 1.3、QUIC v1 和 HTTP/3 的现代规范说明。

一、从 HTTP/1.1 到 HTTP/2

虽然 HTTP/1.1 曾经引入了 pipelining(管线化) 的设计,但它并没有从根本上解决队头阻塞问题。管线化允许客户端在收到前一个响应之前连续发送多个请求,可是服务器仍然必须按照请求顺序返回响应:前面的响应没有完成,后面的响应就不能越过它先返回。

HTTP/1.1 的管线化在实际浏览器中并没有被广泛采用,后来逐渐被 HTTP/2 的多路复用取代。需要注意,HTTP/1.1 客户端通常还会通过建立多个 TCP 连接来提高并发性,因此一个请求的阻塞不一定会阻塞所有连接,但同一 TCP 连接上的请求仍然会受到队头阻塞影响。

这里先回顾一下 HTTP 的发展过程。最初,我们希望通过网络获取文档内容,于是出现了以 GET 请求为代表的 HTTP 通信方式。HTTP/1.0 规定了请求行、响应行、首部等基础语义,但默认情况下一个请求完成后通常会关闭连接;当时也存在 Keep-Alive 等持久连接扩展。

随后制定了 HTTP/1.1。HTTP/1.1 默认支持持久连接,并完善了缓存、条件请求、分块传输和 Host 等机制。HTTP/1.1 曾经是互联网上使用最广泛的版本,不过现在 HTTP/2 和 HTTP/3 也已经广泛部署,具体使用哪个版本取决于客户端、服务器和网络环境。

即使 HTTP/1.1 解决了一部分连接建立和复用问题,它仍然存在应用层队头阻塞问题。假设同一连接中有五个请求被连续发出,如果第一个请求对应的响应迟迟没有完成,后面的响应就可能只能等待它:

HTTP/1.1 队头阻塞示意图

网络通畅时,这种影响可能不明显;但如果请求 1 的数据包丢失、响应延迟或服务器处理缓慢,后续响应也会被拖延。

HTTP/2

HTTP/2 通过 Stream(流)帧(Frame)多路复用(Multiplexing) 改善了 HTTP/1.1 的并发问题。

HTTP/2 会在一个 TCP 连接中建立多个 Stream。一个 HTTP 请求及其响应通常使用一个 Stream,Stream 由端点创建并通过 Stream ID 标识。客户端和服务端都可以创建相应类型的 Stream,具体由 HTTP/2 规范决定。

HTTP/2 还会把请求和响应拆分成二进制帧,例如:

  • HEADERS:传输首部信息;
  • DATA:传输正文;
  • CONTINUATION:继续传输较大的首部块;
  • SETTINGSWINDOW_UPDATERST_STREAM 等控制帧。

多个 Stream 的帧可以在同一个 TCP 连接中交错发送,接收端再按照 Stream ID 和帧顺序重组数据。HTTP/2 还使用 HPACK 压缩首部,并支持优先级等机制。

HTTP/2 多路复用示意图

不过,“HTTP/2 解决了队头阻塞”需要加上范围:

  • 它解决或缓解了 HTTP/1.1 请求响应顺序带来的应用层队头阻塞;
  • 但所有 Stream 仍共享一个 TCP 字节流;
  • 如果 TCP 中间的某些字节丢失,TCP 必须等待重传并按序交付,其他 Stream 也可能受到影响。

因此,HTTP/2 没有解决 TCP 传输层队头阻塞。

二、QUIC 协议

1. QUIC 是什么

QUIC 最初由 Google 提出,后来由 IETF 标准化。QUIC 是一种基于 UDP 的、加密的、多路复用的可靠传输协议,当前标准版本可以参考 RFC 9000~RFC 9002。

QUIC 不是“给 UDP 加一个简单的重传功能”,而是在 UDP 之上重新实现了许多传输层能力,包括:

  • 连接建立和连接关闭;
  • 可靠、有序的 Stream 数据传输;
  • 数据包确认和丢包检测;
  • 流量控制;
  • 拥塞控制;
  • TLS 1.3 握手和加密;
  • Connection ID 和连接迁移。

QUIC 通常作为 HTTP/3 的传输基础,但 QUIC 本身不等同于 HTTP/3。HTTP/3 负责把 HTTP 语义映射到 QUIC Stream 上。

原文将 QUIC 解释为“快速 UDP 互联网连接”,这是 Google 早期名称中的历史解释。现在阅读 QUIC 时,更重要的是理解它的协议能力和 IETF 标准,而不是依赖这个名称解释。

2. QUIC 如何改善 HTTP/2 的队头阻塞

HTTP/2 的多个 Stream 共享一个 TCP 字节流。TCP 必须按字节顺序向应用交付数据,因此一个 TCP 丢包可能阻塞所有 HTTP/2 Stream。

QUIC 同样为每个 Stream 提供可靠、有序的字节流,但可靠性和排序主要在 Stream 内部维护:

  • 某个 Stream 的数据丢失时,需要等待该 Stream 的数据恢复;
  • 不相关 Stream 通常可以继续交付数据;
  • 同一个 Stream 内仍然存在队头阻塞;
  • 连接级拥塞控制受到丢包影响时,整体吞吐量仍可能下降。

因此,准确的说法是:QUIC 避免了 TCP 造成的跨 Stream 队头阻塞,并没有消除所有形式的队头阻塞。

HTTP/3 还使用 QPACK 压缩首部。QPACK 针对 QUIC 的多 Stream 特性进行了设计,但某些动态首部引用仍可能造成请求级别的等待,因此也不能把“队头阻塞完全消失”作为绝对结论。

3. QUIC 的握手为什么可能更快

传统 HTTPS 通常是 HTTP over TCP + TLS:

TCP 连接建立:通常需要 1 RTT
TLS 1.3 握手:通常还需要约 1 RTT
应用数据:握手完成后发送

如果是更早的 TLS 1.2,完整握手可能需要更多往返;如果使用 TLS 会话恢复或 TLS 1.3 0-RTT,也可能减少等待时间。因此不能笼统地说“HTTPS 固定需要 TCP 三次握手加两次 TLS 握手”。

TCP 与 TLS 握手延迟示意图

QUIC 使用 UDP 作为报文承载,并将 QUIC 传输握手与 TLS 1.3 握手结合起来。新连接通常可以在 1-RTT 左右完成握手并获得 1-RTT 应用数据密钥;已有会话恢复信息时,可以尝试使用 0-RTT 发送早期数据。

这里需要注意:

  • QUIC 不是没有握手,而是没有 TCP 的三次握手;
  • QUIC 的 TLS 版本要求是 TLS 1.3;
  • 0-RTT 只适用于会话恢复等特定场景;
  • 0-RTT 数据可能被重放,服务端不能把它当作天然防重放的请求;
  • 支付、转账、修改密码等非幂等操作不应直接依赖 0-RTT。

4. QUIC 如何实现可靠传输

TCP 为了保证可靠性,使用 Sequence Number、Acknowledgment Number、ACK、重传和拥塞控制等机制。TCP 的重传超时时间会根据 RTT、RTT 波动和确认情况动态计算,也会使用快速重传等丢包检测方法。

原文中把 SYN 解释成“synchronize sequence number”,这是不准确的。SYN 是 TCP 报文中的一个标志位;TCP 建连时会交换 Initial Sequence Number(ISN)。TCP 序列号按照发送的字节范围递增,并不是只有收到 ACK 后才递增。

QUIC 不使用 TCP 的 Sequence Number,而是为每个发送方向使用 Packet Number(PN) 标识数据包。QUIC 还有不同的 Packet Number Space,例如 Initial、Handshake、0-RTT 和 1-RTT 空间。Packet Number 在对应空间内严格递增,且不会复用。

QUIC Packet Number 示例

假设发送端发送了 PN = 10 的数据包,但该包丢失,发送端之后仍然可以发送 PN = 11。如果需要恢复 PN = 10 中的 Stream 数据,QUIC 会把相应的 Frame 重新放入一个新的数据包中,并使用新的 Packet Number,而不是原样重用 PN = 10

接收端通过 ACK 范围、包数量阈值和时间阈值等方式帮助发送端判断丢包。例如,接收端可能确认 PN = 11,但没有确认 PN = 10,发送端就可以结合其他信息判断 PN = 10 可能丢失。

QUIC 的 RTT 样本根据数据包发送时间、收到 ACK 的时间以及 ACK 延迟计算。不能把 RTT 简单理解成“数据包在网络中的生存时间”,也不能把 Packet Number 当成 RTT 的直接测量值。

5. Stream Offset 与数据重组

QUIC 的 Stream 是有序字节流。Stream Frame 中包含 Stream ID、Offset 和数据长度等信息,Offset 表示这段数据在对应 Stream 中的字节偏移。

QUIC Stream Offset 示例

例如,一个 Stream 可能包含:

Offset 0   - 999   :第一段数据
Offset 1000-1999  :第二段数据
Offset 2000-2999  :第三段数据

这些数据可能分布在不同的 QUIC Packet 中。即使某个 Packet 丢失,后续 Packet 仍可能到达并被缓存;只有在缺失的 Stream 字节恢复后,该 Stream 才能继续向应用按序交付。

需要区分两个概念:

  • Packet Number:标识 QUIC 数据包;
  • Stream Offset:标识某个 Stream 内的字节位置。

丢包恢复时,QUIC 重新发送需要恢复的 Stream Frame,使用新的 Packet Number,但保留正确的 Stream ID 和 Offset。

QUIC 的可靠性主要针对 Stream 等可靠传输内容。使用 QUIC DATAGRAM 扩展时,数据报可以是不可靠的,不能把 QUIC 的所有数据都简单描述成“必然可靠”。

三、QUIC 的其他特性

1. 用户态实现和可演进性

TCP 的主流实现通常位于操作系统内核中,应用程序只能通过 Socket API 使用它。内核升级涉及操作系统、驱动和兼容性,部署周期通常较长。

QUIC 通常由用户态库实现,应用或服务器可以在不等待操作系统内核更新的情况下升级 QUIC 实现。这是 QUIC 可演进性较强的重要原因之一。

不过,QUIC 仍然是传输层协议,不应简单称为“应用层协议”。拥塞控制和丢包恢复通常由用户态 QUIC 实现负责,但实现仍需符合 QUIC 标准和实际网络约束。不同实现可以选择不同的拥塞控制算法,也不代表可以不受限制地动态切换。

原文提到很多设备仍使用旧操作系统,这是当时解释 QUIC 用户态优势的背景。现在可以保留这个历史背景,但不应把它作为 QUIC 设计的唯一原因;内核态 QUIC、操作系统支持和硬件能力也在不断发展。

2. 流量控制

TCP 使用接收窗口进行流量控制。QUIC 也有流量控制,但除了连接级别,还可以针对每个 Stream 进行限制。

QUIC 主要使用以下帧表达流量控制限制:

  • MAX_DATA:连接级别允许发送的最大数据量;
  • MAX_STREAM_DATA:某个 Stream 允许发送的最大数据量;
  • MAX_STREAMS:限制可创建的双向或单向 Stream 数量。

原文提到的 WINDOW_UPDATE 更常见于 HTTP/2。可以把它作为“窗口更新”的概念类比,但不能把它当作 QUIC 的标准帧名称。

流量控制和拥塞控制也不是同一个概念:

  • 流量控制防止接收方缓冲区被发送方压垮;
  • 拥塞控制根据网络状况控制发送速率,避免网络拥塞。

3. 加密和报文保护

TCP 首部通常没有经过 TLS 级别的加密和认证,TCP/IP 首部以及传输元数据也可能被观察或篡改。QUIC 强制使用 TLS 1.3 来保护连接。

QUIC 的安全性需要准确理解:

  • 应用数据使用 AEAD 算法加密并认证;
  • Packet Number 和部分报文字段使用 Header Protection 进行保护;
  • QUIC Header 并不是全部被加密;
  • IP 和 UDP 首部仍然可见;
  • 连接 ID、版本和部分长首部字段也可能可见。

因此,不应简单说“QUIC 的报文头部全部加密”。更准确地说,QUIC 对载荷进行加密认证,并对部分首部字段进行保护,以降低流量篡改和数据包编号暴露带来的风险。

4. 连接迁移

QUIC 使用 Connection ID 将连接身份与 UDP 的 IP 地址和端口四元组部分解耦。当客户端从 Wi-Fi 切换到移动网络,或者发生 NAT 重绑定时,客户端可以尝试在新的网络路径上继续使用原连接。

连接迁移通常还需要:

  1. 新路径进行路径验证;
  2. 双方支持 Connection ID;
  3. 防火墙和 NAT 允许新的 UDP 流量;
  4. 新路径具备足够的连通性。

因此,连接迁移是 QUIC 的能力,不是所有网络切换都必然成功,也不等于可以完全不进行网络探测或重新验证。

四、QUIC 相比 HTTP/2 的主要优势

总的来说,QUIC 作为 HTTP/3 的传输基础,相比 HTTP/2 over TCP 主要具有以下特点:

  • 基于 UDP 承载,避免了 TCP 三次握手本身,但 QUIC 仍然需要自己的握手;
  • 将 TLS 1.3 握手与传输握手结合,通常可以减少新连接建立的往返次数;
  • 通过独立 Stream 避免 TCP 导致的跨 Stream 队头阻塞;
  • 在用户态实现,协议和算法更容易随应用或服务器升级;
  • 具有连接级和 Stream 级流量控制;
  • 使用 TLS 1.3、AEAD 和 Header Protection 提供安全性;
  • 在满足条件时支持网络地址变化后的连接迁移。

QUIC 也不是在所有场景都一定更快。它仍然受到网络质量、丢包率、拥塞控制、服务器实现、UDP 封锁、代理兼容性和 CPU 开销等因素影响。实际是否启用 HTTP/3,应通过协议协商和真实监控数据判断。

五、QUIC 相关资料与实现

QUIC 协议比较复杂,完整实现需要处理握手、密钥更新、ACK、丢包恢复、拥塞控制、流量控制、Stream 管理、路径验证和连接迁移等内容。直接从零实现完整协议并不容易,实际项目通常选择成熟实现。

原文列出的一些项目具有历史参考价值,但部分已经过时:

1. Chromium 和 QUICHE

原文中的 hanpfei/chromium-net 并不是当前 Chromium 官方 QUIC 实现,不建议作为新项目的主要依赖。Chromium/Google 相关实现可以关注 QUICHE 和 Chromium 网络栈中的 QUIC 代码。

2. proto-quic

proto-quic 是历史项目,已经停止维护,适合阅读旧资料,不适合新项目。

3. goquic

goquic 基于旧版 Chromium/libquic,长期没有跟随现代 QUIC v1 和 HTTP/3 规范更新,不建议用于新的生产系统。

4. quic-go

当前更推荐关注 quic-go/quic-go,它是持续维护的 Go 语言 QUIC 实现,并支持 IETF QUIC、HTTP/3、QPACK 和相关扩展。

5. Caddy

Caddy 是 Web 服务器和反向代理,不是通用 QUIC 协议栈,但它支持 HTTP/3,并能自动管理 HTTPS 证书。使用 Caddy 部署 HTTP/3 时,还要确保防火墙和负载均衡器允许 UDP 443 端口。

六、总结

用几句话概括 QUIC:

  1. HTTP/1.1 管线化受响应顺序限制,存在应用层队头阻塞;
  2. HTTP/2 使用 Stream 和帧实现多路复用,但 TCP 丢包仍会造成跨 Stream 队头阻塞;
  3. QUIC 是基于 UDP 的加密传输协议,自己实现可靠传输、流量控制、拥塞控制和连接管理;
  4. QUIC 使用 TLS 1.3,通常可以把传输握手和加密握手结合起来;
  5. Packet Number 标识数据包,Stream Offset 标识 Stream 内的字节位置,两者不能混淆;
  6. QUIC 解决的是跨 Stream 的传输层队头阻塞,同一 Stream 内仍然按序交付;
  7. HTTP/3 是运行在 QUIC 之上的 HTTP 协议;
  8. 0-RTT、连接迁移和 HTTP/3 都有使用条件,不能绝对化描述。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS