概览: 经过了近5年时间的开发,HTTP/3协议终于接近其最版本了。虽然实验性特性在早起迭代中就可以使用了,但可以预期HTTP/3的可用性和使用率还会在2021继续提升。回到正题上来,HTTP/3究竟是什么?为什么在HTTP/2才诞生不久我们就着手研发它?应该怎么使用HTTP/3?更确切地说应该怎么用它来提升网页性能?请跟随我的步伐一起来了解下。
你可能在之前已经看过一些博客,或者是听过一些公开的会议,你可能认为你已经知道了上面几个问题的答案;又或许你听过类似的言论:HTTP/3在存在丢包的情况下比HTTP/2快很多,或者HTTP/3建立连接的耗时更低并且延时更小,HTTP/3发送数据更快并且可以并行发送更多的资源。
这些言论和文章通常忽略了一些关键的技术细节,以偏概全,而且通常只是部分正确。 但这些言论却把HTTP/3塑造成一种革命性改变,实际上HTTP/3只是小幅度的升级(但依旧有其价值)。这些言论很危险,它们在开发者心目中建立了HTTP/3高性能的预期,但实际上新协议很可能在现实中无法满足这些高预期,从而让大家对新协议感到失望。
我害怕这些是因为过去我们已经在HTTP/2上看到过这样的事了。HTTP/2在诞生前就被鼓吹为令人惊叹的性能革命,它有相当多令人兴奋的新特性,比如服务端推送、流、优先级。 利用这些新特性,我们不需要再做资源绑定,也不需要在多个服务上做资源分片,页面加载的流程将大大简化。只需要轻轻按动一个开关,网站的速度就会神奇地提升50%。
但5年后,实践已经证明服务端推送没有真正发挥作用,流和优先级实现地也很糟糕,从而导致资源绑定和资源分片在某些场景下依旧是很好的实践 (选择)。
除此之外,还有一些扭曲HTTP/2协议行为的机制,比如预加载提示,因为它的一些坑和bug,导致它很难被正确使用。
所以,我觉得对于HTTP/3而言,有必要防止类似错误信息的传播,阻止其在大家心目中为HTTP/3建立起不切实际的预期。
在本系列文章中,我会先介绍下HTTP3协议,尤其是会用更多的细节介绍它的性能特性。我会向大家证明,虽然 HTTP/3 确实有一些有前途的新概念,但遗憾的是,它们对大多数网页和用户的影响可能相对有限(但对一小部分人来说可能至关重要)。 HTTP/3 的设置和使用(正确)也颇具挑战,因此在配置新协议时要格外注意。
为什么我们需要HTTP3?
这也是我经常被问到的一个问题, “为什么仅在HTTP/2标准发布后不久(2015),我们就需要HTTP/3了?”,这确实很奇怪,除非你一开始就意识到其实我们并不是需要一个新的HTTP协议,而是想升级更底层的TCP协议。
TCP是提供关键服务的主要协议,例如向 HTTP 或其他协议提供可靠且有序的数据传输能力。 这也是我们可以继续与许多并发用户一起使用互联网的原因之一,因为它巧妙地将互联网的带宽公平地分给每一个用户。
小知识:
在使用 HTTP(S) 时,实际上是在同时使用除 HTTP 之外的多种协议。 这个“协议栈”中的每个协议都有自己的特性和职责(见下图)。 例如,当 HTTPS 处理 URL 和数据时,传输层安全性 (TLS) 通过加密来确保安全性,TCP 通过重新传输丢失的数据包来实现可靠的数据传输,而互联网协议 (IP) 通过路由设备,将数据包从一端传递到另一端(中间框)。
协议之间这种分层叠加的方式可以更方便地将其各自的特性复用起来。 比如高层协议(如 HTTP)不必重新实现一些复杂的功能(如加密),因为低层协议(如 TLS)已经实现了加密的功能。再比如互联网上大多数应用程序在内部使用TCP协议来确保他们的所有数据被完整传输,所以TCP是互联网上使用和部署最广泛的协议之一。
数十年来,TCP一直是互联网的基石,但在2000年后期,TCP开始逐渐显得有些老态龙钟。而准备替代它的是一种名为QUIC的新传输协议,它在几个关键方面与 TCP 有很大不同,所以很难直接在上面运行HTTP2。 HTTP3主要是为了解决HTTP和QUIC协议的兼容问题,所以它本质上其实是对HTTP2的一个小幅改进,主要是为了让HTTP能使用QUIC那些让人兴奋的新特性。
我们之所以需要QUIC,是因为 TCP 虽然自互联网早期就已经存在,但它在设计之初并没有真正考虑到效率最大化。 例如,TCP 需要“握手”来建立新连接。 这样做是为了确保客户端和服务器都存在,它们愿意交换数据。 然而,握手需要一个完整的网络往返才能完成,然后才能对连接进行后续的操作。 如果客户端和服务器的物理距离较远,那每次网络往返时间 (RTT) 可能会超过 100 毫秒,从而导致明显的延迟。
另外一个例子,TCP 把所有传输的数据看做是单个“文件”或字节流,即使我们实际上是同时使用一个链接来传输多个文件时(例如当我加载由很多个资源组成的网页),这多个文件的数据就会混在一起。 如果其中某个文件数据的 TCP 数据包丢失,那就会导致后续其他的文件延迟,直到丢失的数据包恢复。
这种现象也被称为队头阻塞(head-of-line),虽然队头阻塞的问题在现实中也可以通过其他的方式控制(不然TCP协议也不会被使用超过30多年了),但队头阻塞的问题也确实会明显影响到更高层的协议,比如HTTP。
随着时间的推移,我们尝试改进和升级TCP来改善其中一些问题,甚至为TCP引入了新的性能特性。 例如,TCP Fast Open 通过允许高层协议从一开始就发送数据,从而消除了握手开销。 还有一项被称为 MultiPath TCP 的技术,它可以让你的手机同时使用 WIFI 和 4G 网络传输数据。
实现以上这些 TCP 扩展并不难,难的是如何将其部署在整个互联网上。 因为 TCP 如此流行,几乎所有的网络设备都在硬件层实现了TCP协议,如果这些硬件太旧,或者缺少更新,你们以上提到的一些TCP的新技术将无法使用。
如果我们只关注用户终端设备(例如您的计算机或 Web 服务器),问题就会简单很多,因为这些设备可以相对容易地手动更新。 但是,其他位于客户端和服务器之间的网络设备,比如防火墙、负载均衡器、路由器、缓存服务器、代理等,它们之上都有自己关于TCP相关功能的实现。
但像防火墙、负载均衡器、路由器等设备一般都有更严格的限制,所以通常很难升级。就拿防火墙来说,它的主要功能就是为了阻止某些未知的流量,而这些功能很多都是基于TCP协议实现的。实际上除了防火墙之外,还有大量的中间层基于TCP协议做了很多事情。
如果你想要升级TCP协议,至少要十多年才能让这些中间层都逐渐升级上来,而且完全不可控,所以可以怎么说,TCP已经几乎不可能再演进了。
因此,很明显我们需要一个新协议来替代 TCP 协议,而不是升级 TCP 协议。但是,TCP 协议发展了这么多年,其各种功能和实现都变得非常复杂,从头开始构建一个新的协议也很艰难。所以,在2010年初,升级TCP协议这事被搁置了。
其实,不仅 TCP 存在问题,HTTP/1.1 也存在问题。 我们选择把HTTP的升级工作拆开,首先“修复” HTTP/1.1,从而形成现在的 HTTP/2。 之后,可以开始用新协议替换掉其中的 TCP,即现在的 QUIC。 起初,我们希望能直接在QUIC之上直接运行 HTTP/2,但实践后证明这效率太低了(主要是因为很多功能是重复的)。
取而代之的是,我们在HTTP/2在几个关键领域进行了调整,使其与 QUIC 兼容。 这个经过调整的版本最终被命名为 HTTP/3(而不是 HTTP/2-over-QUIC),主要是出于营销原因和清晰度。 因此,HTTP/1.1 和 HTTP/2 之间的差异比 HTTP/2 和 HTTP/3 之间的差异要大得多。
小结
这里的关键点是,我们需要的不是真正的 HTTP/3,而是“TCP/2”,HTTP/3只是我们在这个过程中附带得到的。我们对 HTTP/3 感到兴奋的主要特性(更快的连接设置、更少的 HoL 阻塞、连接迁移等)实际上都来自 QUIC。
什么是QUIC?
你可能想知道为什么这很重要?谁在乎这些功能是在 HTTP/3 还是 QUIC 中?我觉得这很重要,因为 QUIC 是一种通用传输协议,它与 TCP 非常相似,除了 HTTP 和网页加载之外,它还可以被用在很多地方。比如,DNS、SSH、SMB、RTP 等都可以在 QUIC 上运行。因此,让我们先深入地了解一下 QUIC,因为我看到过的大多是关于 HTTP/3 的误解都来源于此。
您可能听说过 QUIC 在用户数据报协议 (UDP)而非 TCP 之上。这的确是真的,但不是出于许多人声称的(性能)原因。理想情况下,QUIC 应该是一个完全独立的新传输协议,就像上图中的TCP一样,它应该直接运行在IP协议栈之上。
但是如果完全重新创造一个新协议的话,我们又会遇到TCP刚开始发展时候遇到的问题,我们必须得等互联网上所有的设备升级之后,才能使用QUIC协议,无疑这不大可能。但幸运的是,我们可以在互联网上另一种广泛被支持的协议—— UDP 之上构建 QUIC 。
小知识:
UDP 是最简单的传输协议。 除了所谓的端口号(例如,HTTP 使用端口 80,HTTPS 使用 443,DNS 使用端口 53)之外,它实际上并没有提供任何功能。 它不需要通过握手来建立连接,它也不可靠:如果 UDP 数据包丢失,它不会自动重传。 因此,UDP 的“尽力而为”方法意味着它的性能几乎与您所能获得的一样:无需等待握手,也没有 HoL 阻塞。 在实践中,UDP 主要用于以高速率更新的实时流量,因此几乎不会受到丢包的影响,因为无论如何丢失的数据很快就会过时(例如实时视频会议和游戏)。对于需要低前期延迟的情况也很有用; 例如,DNS 域名查找实际上应该只需要一次往返即可完成。
许多人声称,由于性能原因,HTTP/3 构建在 UDP 之上。 他们说 HTTP/3 更快,因为就像 UDP 一样,它不建立连接,也不等待数据包重传。 当然这些说法是错误的。 正如我们上面所说,QUIC 使用了 UDP,主要是因为基于 UDP 可以让 HTTP/3 协议推广和使用变得简单,因为互联网上的(几乎)所有设备都已经支持了 UDP 协议。
另外,在 UDP 之上,QUIC 基本上重新实现了那些让 TCP 成为如此强大和流行(但速度稍慢)协议的功能。 QUIC 是绝对可靠的,它使用确认和重传机制来保证数据不丢失。 QUIC 也仍需建立连接并且也有高度复杂的握手机制。
最后,TCP 使用了所谓的流量控制和拥塞控制机制来防止网络过载,但这会让 TCP的 传输比 UDP 传输更慢一些。 QUIC 也使用了类似的机制来避免网络过载,但 QUIC 在一些核心功能上参考了 TCP 数十年的部署经验和一些最佳实践,所以 QUIC 的对于流量控制做的比 TCP 更智能、更高效。我们将在本文后面更深入地讨论这些功能。
小结
天下没有免费的午餐。 HTTP/3 并没有因我们将 TCP 换成了 UDP就神奇地比HTTP/2 快。 相反,我们重新构想并实现了更高级的 TCP 版本,并将其称为 QUIC。 因为我们想让 QUIC 更容易部署,所以才选择了 UDP。
QUIC的重大变革!
那么,QUIC 究竟是如何改进 TCP 的呢?QUIC 和 TCP 有什么不同? QUIC 中有哪些新的功能和新的机遇(0-RTT 数据、连接迁移、对丢包和慢速网络的更强弹性)?我们将在本系列的下一部分中详细讨论。 然而,所有这些新事物基本上归结为四个主要变化:
- QUIC和TLS协议深度集成。
- QUIC支持多个独立的字节流。
- QUIC使用链接ID。
- QUIC使用帧。
接下来我们展开看下每一点。
没有TLS协议就没有QUIC协议
如上文所述,TLS(Transport Layer Security protocal 传输层安全协议)负责保护和加密通过互联网传输的数据。 当你在使用 HTTPS 时,您的纯文本 HTTP 数据会先由 TLS 加密,然后通过 TCP 传输。
小知识:
这里你不需要了解TLS 的技术细节,你只需要知道加密是使用一些非常高级的数学知识和非常大的(素数)数字完成的。 这些数学参数在客户端和服务器之间在单独的特定于 TLS 的加密握手期间协商。 就像 TCP 握手一样,这种协商可能需要一些时间。 在旧版本的 TLS(例如 1.2 及更低版本)中,这通常需要两次网络往返。 在较新版本的 TLS(1.3 是最新版本)中,已经只需要一次网络往返。 主要是因为 TLS 1.3 将可以协商的不同数学算法严格限制为少数几个(最安全的算法)。 这意味着客户端可以直接确认服务器将支持哪些协议,而不必等待服务端返回支持的列表,从而节省了网络往返时间。
在互联网的早期,对流量做加密的成本很高。此外,加密的必要性也不被认可。从历史上看,TLS 一直是一个完全独立的协议,可以选择在 TCP 之上使用。这就是我们区分 HTTP(没有 TLS)和 HTTPS(有 TLS)的原因。
随着时间的推移,我们对互联网安全的态度当然已经转变为“默认安全”。因此,虽然理论上 HTTP/2 可以在没有 TLS 的情况下直接在 TCP 上运行(这甚至在 RFC 规范中定义为明文 HTTP/2),但流行的)Web 浏览器实际上都没有支持这种模式。在某种程度上,浏览器供应商也有意识地以性能为代价换取了更高的安全性。
鉴于这种朝着始终在线的TLS(尤其是 WEB 流量)的明显演变,QUIC 的设计者决定顺应这一发展趋势,将其提升到一个新的水平。他们没有为 HTTP/3 定义明文模式,而是直接选择将数据加密融入 QUIC 本身中。虽然 Google 实现的 QUIC 版本使用了自定义的特殊配置,但标准化的 QUIC 是直接使用现有的 TLS 1.3 协议 。
为此,QUIC 打破了协议栈中协议的隔离性,正如我们在上图中看到的那样。虽然 TLS 1.3 仍然可以在 TCP 之上独立运行,但 QUIC 将 TLS 1.3 封装了起来。换句话说,没有 TLS 就无法使用 QUIC; QUIC(以及扩展的 HTTP/3)数据始终是完全加密的。此外,QUIC 还加密了几乎所有的数据包头字段。 网络中的中间设备将无法读取到传输层信息(例如 TCP 从不加密的数据包编号),但 QUIC 中却是加密的(甚至某些数据包标头标志已加密)。
正是因为 QUIC 集成了 TLS1.3,所以它或多或少地使用 TLS 1.3 握手来构建数学加密参数。但之后,QUIC 会接管并自行加密数据包,而在 TCP 依旧是 TLS 进行数据加密。 这种看似细小的差异,却代表了数据加密理念的变化,数据加密会在越来越底层的协议中执行。
这种方式为QUIC提供了几个好处:
-
QUIC 对用户而言更安全。
禁止了明文使用 QUIC 协议,因此攻击者和窃听者监听的选项也更少。(最近的研究表明 HTTP/2 的明文选项有多危险)
-
QUIC 链接建立更快。
虽然对于 TLS-over-TCP,两种协议都需要自己单独的握手,但 QUIC 将传输和加密握手合二为一,从而节省了往返行程(见上图)。 我们将在第二部分中更详细地讨论这一点。
-
QUIC 升级起来更简单。
**因为它是完全加密的,所以网络中的中间设备不能再像使用 TCP 一样观察和干扰其工作行为。 因此,也就不会出现因为 QUIC 版本升级导致的不可用。 如果我们想在未来为 QUIC 添加新功能,只需要更新终端设备即可。
然而,除了这些好处之外,大量加密也有一些潜在的缺点:
-
许多网络会犹豫是否允许 QUIC。
QUIC 会导致防火墙很难检测流量,也会导致ISP和网络中间商无法获取到平均延时和丢包比例数据,从而很难诊断和排查问题,这些都会导致大家不太想用 QUIC ,从而导致 QUIC 普及困难。
-
QUIC 具有更高的加密开销。
QUIC 使用 TLS 加密每个单独的数据包,而 TLS-over-TCP 可以同时加密多个数据包。这可能会使 QUIC 在高吞吐量场景中变慢(我们将在第 2 部分中看到)。
-
QUIC 使网络更加中心化。
我经常遇到的抱怨是,“Google 推动着 QUIC,因为 QUIC 让 Google 可以完全访问数据,而不会与其他人共享任何数据”。我绝对不同意这一点。首先,QUIC 与 TLS-over-TCP 相比,QUIC 不会向外部观察者隐藏更多(或更少)用户级信息(例如,您正在访问的 URL)(QUIC会保持现状)。
其次,虽然谷歌发起了 QUIC 项目,但我们今天谈论 QUIC 最终版协议其实是由互联网工程任务组 (IETF)所设计的。 IETF 的 QUIC 在技术上与 Google 的 QUIC 有很大的不同。 坦诚地讲,IETF 的成员确实大部分来自 Google 和 Facebook 等大公司以及 Cloudflare 和 Fastly 等 CDN厂商。 由于 QUIC 的复杂性,QUIC协议的发展不得不依赖于这些大公司的实践经验和知识。 这可能会 QUIC 协议会被这些公司所主导,这确实是一个真正令人担忧的问题。
作者注: 这也是我写这些类型的文章并进行大量技术讨论的原因之一:确保更多的人了解协议的细节,并且可以独立于这些大公司使用它们
小结
这里的关键点是 QUIC 默认是深度加密的。 这不仅提高了其安全性和隐私性,还有助于其可部署性和可演化性。 它使协议运行起来有点重,但作为回报,它也优化了一些其他地方,例如可以以更快的速度建立连接。
QUIC的多数据流
TCP 和 QUIC 之间的第二大区别是QUIC的技术性更强,我们将在第 2 部分更详细地探讨它的影响。不过,现在,我们可以在更的高层次上了解其主要特点。
小知识:
即使是一个简单的网页也是由许多独立的文件和资源组成的。 有 HTML、CSS、JavaScript、图像等等。 这些文件中的每一个都可以被视为一个简单的“二进制流”——由浏览器以某种方式解释的 0 和 1 的集合。 通过网络发送这些文件时,我们不会一次全部传输它们。 相反,它们被细分为更小的块(通常每个大约 1400 字节)并以单个数据包的形式发送。 因此,我们可以将每个资源视为一个单独的“字节流”,因为数据会随着时间的推移而零碎地下载或“流式传输”。
对于 HTTP/1.1,资源加载过程非常简单,因为每个文件都有自己的 TCP 连接并完整下载。 例如,如果我们有文件 A、B 和 C,我们将有三个 TCP 连接。 第一个将看到 AAAA 的字节流,第二个 BBBB,第三个 CCCC(每个字母重复是一个 TCP 数据包)。 这可行,但效率也很低,因为每建立一个新连接都有一定的开销。
实际情况下,浏览器可能会限制同一个网站的并发链接数(可以并行下载多少个文件)——通常在每个页面限制是6到30个之间。一旦前一个文件完全传输,然后才能重新建立连接下载新的文件。这些限制最终开始影响那些加载超过30个资源的页面的性能。
HTTP/2 的主要目标之一就是改善这种情况。该协议不再为每个文件打开一个新的 TCP 连接,而是通过不同的字节流复用同一个连接来实现(多路复用)单个 TCP 连接下载多个不同的资源。在我们传输数据时将不同的文件混合在一起,看起来很奇怪。对于上面三个文件的示例,我们将建立一个 TCP 连接,在其中传入的数据可能看起来像 AABBCCAABBCC(尽管许多其他排序方案是可能的)。这看起来很简单,而且确实工作得很好,使 HTTP/2 通常与 HTTP/1.1 一样快或快一点,但开销要少得多。
让我们来近距离看下不同:
另外,TCP 端也存在问题。 因为 TCP 是一个古老的协议,它仅仅是用于加载网页,它也不知道 A、B 或 C。在 TCP 内部,它认为它只传输单个文件 X,它不也不关注 XXXXXXXXXXXX 实际上是 HTTP 级别的 AABBCCAABBCC。 在大多数情况下,这无关紧要(它实际上使 TCP 非常灵活!),但是当网络上出现丢包等情况时,情况就会发生改变。
假设第三个 TCP 数据包丢失(包含文件 B 的第一个数据的数据包),但所有其他数据都已传递。 TCP 通过在新数据包中重新传输丢失数据的新副本来处理这种丢失。 但是,此重传可能需要一段时间才能到达(至少需要一个 RTT)。 您可能认为这不是什么大问题,因为我们看到资源 A 和 C 没有丢失。因此,我们可以在等待 B 丢失数据的同时开始处理它们,对吧?
可悲的是,事实并非如此,因为重传逻辑发生在 TCP 层,而 TCP 不知道 A、B 和 C! 相反,TCP 认为单个 X 文件的一部分已经丢失,因此它认为它必须阻止 X 的其余数据被处理,直到缺失的数据被补齐。 换句话说,虽然在 HTTP/2 级别,我们知道我们已经可以处理 A 和 C,但 TCP 不知道这一点,这就会导致数据传输比我们预期的要慢。 这被称为“队头阻塞(HOL)”问题。
解决传输层的 HoL 阻塞便是 QUIC 的主要目标之一。 与 TCP 不同,QUIC 清楚地意识到它正在复用多个独立的字节流。 当然,它也不知道它正在传输 CSS、JavaScript 和图像。 它只知道流是分开的。 因此,QUIC 可以在每个流的基础上执行丢包检测和恢复逻辑。
在上面的场景中,它只会保留流 B 的数据,与 TCP 不同的是,它会尽快将 A 和 C 的任何数据传递到 HTTP/3 层。 (如下图所示。)理论上,这可以提高性能。 然而,在实践中稍微有些复杂,我们将会在第 2 部分中详细讨论。
我们可以看到,我们现在在 TCP 和 QUIC 之间有了本质的区别。 顺便说一句,这也是我们不能在 QUIC 上那样运行 HTTP/2 的主要原因之一。 正如我们所说,HTTP/2 还包括在单个 (TCP) 连接上运行多个流的概念。 因此,HTTP/2-over-QUIC 将有两种不同且相互竞争的流抽象。
想让它们能正确地协同工作非常困难; 因此,HTTP/2 和 HTTP/3 之间的主要区别之一是后者删除了 HTTP 流逻辑并用了 QUIC 流替代了它。 但是,正如我们将在第 2 部分中看到的那样,这会对实现服务器推送、标头压缩和优先级等功能产生其他影响。
小结
这里的关键点是 TCP 从未设计为通过单个连接传输多个独立文件。 因为这正是网络浏览所需要的,所以多年来这导致了许多低效率。 QUIC 通过将多字节流作为传输层的核心概念并在每个流的基础上处理数据包丢失来解决这个问题。
QUIC支持链接迁移
QUIC 的第三个主要改进是可以长时间保持连接。
小知识:
我们在谈论 Web 协议时,我们经常使用“连接”的概念。 然而,究竟什么是连接? 通常而言,人们说的连接就是两个端(例如,浏览器或客户端与服务器)之间发生了握手。 这就是为什么 UDP 经常(有点被误导)被称为“无连接”的原因,因为它不做这样的握手。 然而,握手实际上并没有什么特别之处:它只是几个具有特定形式的数据包被发送和接收。 它有几个目的,其中主要是确保另一端有内容,并且愿意发送给我们。 值得一提的是,虽然UDP本身不会有握手,但几月UDP实现的QUIC 也会执行握手。
那么,问题就变成了,这些数据包如何到达正确的目的地? 在互联网上,IP 地址用于在两台不同的机器之间路由数据包。 然而,仅仅拥有手机和服务器的 IP 是不够的,因为两者都希望能够在每一端同时运行多个联网程序。
这就是为什么每个单独的连接也需要在两端各分配一个端口号以区分连接和它们所属的应用程序。 服务器应用程序通常根据其功能有一个固定的端口号(例如端口 80 和 443 用于 HTTP(S),端口 53 用于 DNS),而客户端通常为每个连接(半)随机选择其端口号。
因此,要定义跨机器和应用程序的唯一连接,我们需要这四个东西,即所谓的IP4元组:客户端 IP 地址 + 客户端端口 + 服务器 IP 地址 + 服务器端口。
在 TCP 中,连接仅由4元组标识。 因此,如果这四个参数中只有一个发生了变化,则连接将变为无效并需要重新建立(包括新的握手)。 要理解这一点,请想象一下停车场问题:您当前在有 Wi-Fi 的建筑物内使用智能手机。 因此,您在此 Wi-Fi 网络上有一个 IP 地址。
如果你现在走到外面,您的手机可能会切换到蜂窝 4G 网络。 因为这是一个新网络,你的手机会得到一个全新的 IP 地址,因为这些是特定于网络的。 现在,服务器将看到来自它以前从未见过的客户端 IP 的 TCP 数据包(当然,这两个端口和服务器 IP 可以保持不变)。 如下图所示:
但是服务器怎么知道这些来自一个新IP的数据包属于“连接”呢? 它如何知道这些数据包不属于来自蜂窝网络中另一个客户端的新连接,该客户端选择了相同的(随机)客户端端口(这很容易发生)? 不幸的是,它无法知道这一点。
因为TCP的发明甚至在我们还在梦想蜂窝网络和智能手机之前,例如,没有任何机制允许客户端让服务器知道它已经更改了 IP。 甚至没有办法“关闭”连接,因为发送到旧 4 元组的 TCP 重置或 fin 命令甚至无法再到达客户端。 因此,在现实中,每次网络更改都意味着无法再使用现有的 TCP 连接。
必须执行新的 TCP(可能还有 TLS)握手以建立新连接,并且根据应用程序级协议,需要重新启动进程中的操作。 例如,如果您通过 HTTP 下载一个大文件,则可能必须从一开始就重新请求该文件(例如,如果服务器不支持范围请求)。 另一个例子是实时视频会议,在切换网络时您可能会遇到短暂的卡顿。
请注意,4 元组还有可能因为其他的原因(例如,NAT 重新绑定),我们将在第 2 部分中详细讨论。
由此可见,重新创建TCP连接可能会产生严重影响(等待新的握手、重新启动下载、重新建立上下文)。 为了解决这个问题,QUIC引入了一个名为连接标识符(CID)的新概念。 每个连接在 4 元组之上分配了另一个数字,用于在两个端数据连接的唯一标识。
至关重要的是,因为这个 CID 是在 QUIC 本身的传输层定义的,所以在网络之间移动时它不会改变! 如下图所示。 为了实现这一点,CID 包含在每个 QUIC 数据包的前面(很像 IP 地址和端口也出现在每个数据包中)。 (它实际上是 QUIC 数据包头中为数不多的未加密的东西之一!)
通过这种设置,即使 4 元组中的某一项发生了变化,QUIC 服务器和客户端只需查看 CID 就知道它是同一个旧连接,然后他们可以继续使用它。 不需要重新握手,下载状态可以保持原样。 此功能通常称为连接迁移。 从理论上讲,这对性能更好,但正如我们将在第 2 部分中讨论的那样,这当然又是一个微妙的故事。
CID 还有其他挑战需要克服。 例如,如果我们确实只使用一个 CID,那么黑客和窃听者就可以非常容易地跨网络跟踪用户,进而推断出他们的(大致)物理位置。 为了防止这种隐私噩梦,QUIC 每次使用新网络时都会更改 CID。
不过,这可能会让您感到困惑:我刚才不是说 CID 在网络中应该是相同的吗? 好吧,刚才说的过于简化了,实际上真正在内部发生的是客户端和服务器在一个共同的(随机生成的)CID 列表上达成一致,这些 CID 都映射到相同的概念“连接”。
例如,他们都知道 CID K、C 和 D 实际上都映射到连接 X。因此,虽然客户端可能在 Wi-Fi 上使用 K 标记数据包,但它可以切换到在 4G 上使用 C。 这些通用列表在 QUIC 中协商完全加密,因此潜在的攻击者不会知道 K 和 C 真的是 X,但客户端和服务器会知道这一点,并且他们可以让连接处于活动状态。
这让事情变得更加复杂了,因为客户端和服务器将拥有 CID 列表供它们自己选择(就像它们具有不同的端口号一样)。 这主要是为了支持大规模服务器设置中的路由和负载平衡,我们将在第 3 部分中更详细地看到。
小结
这里的关键点是,在 TCP 中,连接由四个参数定义,这些参数可以在设备端网络变更时更改。 因此,这些连接有时需要重新启动,从而导致一些停机时间。 QUIC 添加了另一个参数,称为连接 ID。 QUIC 客户端和服务器都知道哪些连接 ID 映射到哪些连接,从而屏蔽掉网络的变化。
QUIC 是灵活和可发展的
QUIC 的最后一个方面是它经过专门设计,易于发展。 这是通过几种不同的方式实现的。 首先,正如所讨论的,QUIC 几乎完全加密的事实意味着如果我们想要部署更新版本的 QUIC,我们只需要更新两端设备即可(客户端和服务器),而不是所有中间部分。 虽然这仍然需要时间,但通常几个月就足够了,而中间网络设备的升级可能需要几年时间。
其次,与 TCP 不同,QUIC 不使用单个固定的数据包头来发送所有协议元数据。 相反,QUIC 具有较短的数据包标头,并在数据包有效负载内使用各种“帧”(有点像微型专用数据包)来传达额外信息。 例如,有一个 ACK 帧(用于确认)、一个 NEW_CONNECTION_ID 帧(用于帮助设置连接迁移)和一个 STREAM 帧(用于承载数据),如下图所示。
这主要是作为一种优化完成的,因为不是每个数据包都携带所有可能的元数据(因此 TCP 数据包标头通常会浪费相当多的字节 - 另见上图)。 然而,使用框架的一个非常有用的附带作用是,将来定义新的框架类型作为 QUIC 的扩展会非常容易。 例如,一个非常重要的框架是 DATAGRAM 帧,它允许通过加密的 QUIC 连接发送不可靠的数据。
第三,QUIC 使用自定义 TLS 扩展来携带所谓的传输参数。 这些允许客户端和服务器为 QUIC 连接选择配置。 这意味着他们可以协商启用哪些功能(例如,是否允许连接迁移、支持哪些扩展等)并传达某些机制的合理默认值(例如,支持的最大数据包大小、流量控制限制)。 虽然 QUIC 标准定义了一个很长的列表,但它也允许扩展定义新的配置,再次使协议更加灵活。
最后,虽然我们需要的并不是 QUIC 协议本身,但目前大多数实现都是在“用户空间”中完成的(与 TCP 不同,后者通常在“内核空间”中完成)。 我们会在第 2 部分中讨论细节,但这主要意味着与 TCP 相比,试验和部署 QUIC 实现变体和扩展要容易得多。
小结
虽然 QUIC 现在已经标准化,但它确实应该被视为 QUIC 版本 1(在征求意见 (RFC) 中也明确说明了这一点),并且有明确的意图创建版本 2 并且更快地创建。 最重要的是,QUIC 允许轻松定义扩展,因此可以有更多用例实现。
总结
让我们总结一下我们在这一部分中学到的东西。我们主要讨论了无处不在的 TCP 协议,以及它是如何在当今许多挑战未知的时代设计的。当我们试图发展 TCP 以跟上时代发展时,很明显这会很困难,因为几乎每个设备都有自己的 TCP 实现,并且都需要更新。
为了在改进 TCP 的同时绕过这个问题,我们创建了新的 QUIC 协议(实际上是 TCP 2.0)。为了让QUIC 更易于部署,我们将它运行在 UDP 协议(大多数网络设备也支持)之上,并确保它可以在未来升级。默认情况下它几乎完全加密,并使用灵活的框架机制。