HTTP/3 和 HTTP/2 以及 HTTP/1.1 最大的不同应该是 HTTP/3 颠覆了 HTTP 使用 TCP 的「传统」。如果要写一系列和 HTTP/3 相关的文章,不能不提一提 QUIC 的故事。

硬币:HTTP/2 的正面和反面

在这之前,我用了三四千字写了一篇 HTTP/2 三大特性的介绍、以及讲述了如何将其用于更快速的递送静态资源,如果你还没有读过那篇「静态资源递送:HTTP/2 和 Server Push」的话,其实无伤大雅,反正我要把 HTTP/2 再总结一次。

HTTP/2 的多路复用使得只需要一个 TCP 连接、一股数据流就可以递送多个静态资源,在这基础上 HPACK 头部压缩技术使得在响应头上消耗的流量进一步减少。但是,HTTP/2 带来最深远的变革是,一个资源需要一个 TCP 连接的时代过去了。

但是,HTTP/2 毕竟还是基于 TCP 的。丢包重传注定要面对「头部阻塞」。如果一个 TCP 连接中某个数据包丢失,那么整个 TCP 数据流必须暂停,丢失的数据包需要重新传输,然后数据流才能继续。在弱网环境下(想象一下宇宙射线、运营商 QoS、骨干网阻塞、部署不当的 4G 基站、打开了的微波炉都会导致数据包丢失),HTTP/2 的性能甚至不如 HTTP/1.1,因为 HTTP/2 只使用一个 TCP 连接,而在 HTTP/1.1 的年代人们会通过域名散列的方式同时使用六个连接、(暂时)没有受到丢包影响的 TCP 连接仍然可以继续传输数据。

快,造个新的轮子!

如果 TCP 的头部阻塞问题无法被解决,为什么不发明一个新的协议呢?

坏消息是,现在互联网不是这么工作的。为了保证互联网的存在和工作,需要各式各样的设备:防火墙、NAT、路由器,还有一大堆其他的玩意。正是这些玩意组成了我们的互联网。互联网服务提供商(ISP)采购的设备有(很大的)可能支持常规的 TCP 和 UDP。除此以外,操作系统内核中关于网络栈的实现也需要一起更新。

对那些组成互联网的设备全部进行更新换代、以及让一堆绞尽脑汁关闭 Windows Update 的用户升级他们的操作系统,这是一项耗时漫长、不切实际且几乎不可能完成的任务。

UDP、TCP 或者,SCTP?

如果不能发明一个新的协议,那可以从 IETF 制定的 RFC 里碰碰运气、找找有没有符合要求的协议。一个想法是 HTTP 应该基于 SCTP —— 流控制传输协议,这一协议的相关规范由 RFC4960 制定。更棒的是,这个协议甚至还有现成的 UDP 实现(WebRTC)。但是:

  • SCTP 没有 TLS 支持
  • SCTP 建立连接还是需要 4 次握手
  • SCTP 在连接建立时需要协商并固定允许的数据流数量
  • SCTP 不支持 NAT

当然,也有破坏性不那么大的、对现有的 TCP 进行改造的方案。RFC7143 提出了 TCP Fast Open(一般被简称为 TFO),客户端在第一个 TCP SYN 报文中就可以包含有效数据。RFC7143 发布于 6 年前,但是对 TFO 提供兼容的操作系统和硬件仍然寥寥无几。而且似乎,针对 TFO 的 QoS 比 TFO 本身的发展还要快得多。

拥抱 QUIC

QUIC 不是什么缩写词(虽然它每个字母都是大写的)。QUIC 的意思就是 quick(有趣的事实,SPDY 的意思是 speedy),目的是为了在 TLS 的基础上修补当时 SPDY(HTTP/2 的前身)未能解决的缺陷。

正如其名,QUIC 在设计之初也是为了将数据更快的交付给最终用户。QUIC 在设计时允许在一个连接上建立两个独立平行的数据流,如果遭遇了丢包,只有某一个数据流需要暂停、而另一个数据流并不受影响。和依赖 TCP 三次握手加上 TLS 一次握手相比,QUIC 提供了 0-RTT 和 1-RTT 的握手,大大减少了协商建立新连接所需的时间;相比 TFO,QUIC 的「Early Data」在实现和使用上更加简便。

在保证快速的同时,QUIC 在设计时也强调了安全性。和 HTTP/2 不同(只有浏览器在实现 HTTP/2 时要求使用 HTTPS),QUIC 不被允许使用明文、所有握手和传输都需要加密,只有在进行加密协议协商时才会发送数个明文握手报文。

所以 QUIC 是从哪里来的?

早在 2012 年至 2015 年这段时间(是的,TFO 差不多也是那个时候提出来的),Google 就开始着手于一系列实验,这些实验的成果就是 gQUIC,类似下图的千层饼:

  1. +-----------------+
  2. | HTTP Semantics |
  3. +-----------------+
  4. | SPDY Syntax |
  5. +-----------------+
  6. | QUIC Packets |
  7. +-----------------+
  8. | UDP Protocol |
  9. +-----------------+

在当时,HTTP/2 还没有正式完成,因此 Google 仍通过 SPDY 二进制编码 HTTP 语义,同时将 SPDY 编码封装在 QUIC 数据包中通过 UDP 传输,一般被称为 gQUIC。但是,由于对 SPDY 编码方式的应用,意味着 gQUIC 只能被用于 HTTP 协议。随着 HTTP/2 正式发布,Google 也不断迭代 gQUIC、不断将 HTTP/2 的语法和编码方式添加到 gQUIC 中。

值得注意的是,gQUIC 并不通过 TLS 提供安全性。Google 造了一个新的加密方案的轮子,称作「QUIC Crypto」,而其中「快速安全握手」这一特性(复用先前会话的 Session 等信息)后来被合并到 TLS 1.3,成为 0-RTT 的一部分。

在 IETF 小组参与 QUIC 规范化后,为了改善普适性,IETF 提出了 HTTP over QUIC 和 QUIC 传输层两个概念,将 QUIC 分别用于 HTTP 和其他类型的数据传输。从这一刻开始,Google 的 gQUIC 和 iQUIC(也被称作 IETF-QUIC)开始逐渐分道扬镳(虽然 Google 继续在 gQUIC 迭代中合并 IETF 的进展、以期获得 iQUIC 的相关经验)。之后,IETF 的 HTTP over QUIC 成为了如今被称为 HTTP/3 的东西,而将 QUIC 用于其他数据传输的草案至今仍未落地。

本文作者 : Sukka
本文采用 CC BY-NC-SA 4.0 许可协议。转载和引用时请注意遵守协议、注明出处!
本文链接 : https://blog.skk.moe/post/http3-from-spdy-to-quic/