为什么我们需要 HTTP/3?
我经常遇到的一个问题是,“为什么在 HTTP/2(在 2015 年才标准化)之后不久我们就需要 HTTP/3?”这确实让人感到奇怪,直到你意识到我们首先并不真的需要一个新的 HTTP 版本,而是需要对底层的传输控制协议(TCP)进行升级。
TCP 是提供关键服务(如可靠性和顺序交付)给其他协议(如 HTTP)的主要协议。它也是我们可以在许多并发用户的情况下继续使用互联网的原因之一,因为它聪明地限制了每个用户的带宽使用量,确保公平分享。
你知道吗?
当使用 HTTP(S) 时,你实际上同时使用了 HTTP 之外的几个协议。这个“协议栈”中的每个协议都有其自己的功能和责任(见下图)。例如,HTTP 负责处理 URL 和数据解释,传输层安全性(TLS)通过加密确保安全性,TCP 通过重新传输丢失的数据包来实现可靠的数据传输,而互联网协议(IP)将数据包从一个端点路由到另一个端点,跨越中间的不同设备(中间设备)。
协议相互之间的“分层”是为了允许轻松重用它们的功能。高层协议(如 HTTP)不必重新实现复杂的功能(如加密),因为低层协议(如 TLS)已经为他们做了。再举一个例子,互联网上的大多数应用都在内部使用 TCP 来确保他们的所有数据完整传输。因此,TCP 是互联网上最广泛使用和部署的协议之一。
TCP 几十年来一直是网络的基石,但在 2000 年代后期开始显现出年代的痕迹。其预期的替代品,一个名为QUIC的新传输协议,在几个关键方面与 TCP 有足够的不同,使得在其之上直接运行HTTP/2变得非常困难。因此,HTTP/3 本身是 HTTP/2 的一个相对较小的调整,以使其与新的 QUIC 协议兼容,后者包含了人们期待的大多数新特性。
由于 TCP 自互联网早期以来一直存在,最初并没有以最大效率为考虑目标设计。例如,TCP 要求进行一个“握手”来建立新连接。这是为了确保客户端和服务器存在,且愿意且能够交换数据。然而,这也意味着在连接上可以执行任何其他操作之前需要完成一个完整的网络往返。如果客户端和服务器地理位置遥远,每次往返时间(RTT)可能超过 100 毫秒,从而造成可感知的延迟。
作为第二个例子,TCP 将其传输的所有数据视为单个“文件”或字节流,即使我们实际上使用它同时传输多个文件(例如,当下载由许多资源组成的网页时)。实际上,这意味着如果包含单个文件数据的 TCP 数据包丢失,则所有其他文件的传输也将延迟,直到这些数据包被恢复。
这就是所谓的队首阻塞(HoL 阻塞) 。虽然这些低效率在实践中是可管理的(否则我们不会使用 TCP 超过 30 年),但它们确实以明显的方式影响了 HTTP 等高层协议。
随着时间的推移,我们试图发展和升级 TCP 以改善这些问题,甚至引入新的性能特性。例如,TCP Fast Open通过允许高层协议从一开始就发送数据来消除握手开销。另一个努力称为MultiPath TCP。这里的想法是,你的手机通常同时拥有 Wi-Fi 和(4G)蜂窝连接,那么为什么不同时使用它们以获得更多的吞吐量和鲁棒性呢?
实现这些 TCP 扩展并不是特别困难。然而,要在互联网规模上实际部署它们是极其具有挑战性的。由于 TCP 如此流行,几乎每个连接设备都自带了自己的协议实现。如果这些实现过于陈旧、缺乏更新或存在漏洞,那么这些扩展就无法实用。换句话说,所有实现都需要知道扩展,才能使其有用。
如果我们只谈论终端用户设备(如你的电脑或 Web 服务器),因为它们可以相对容易地手动更新,那么这就不是一个大问题。然而,客户端和服务器之间还有许多其他设备,它们也有自己的 TCP 代码(例如防火墙、负载均衡器、路由器、缓存服务器、代理等)。
这些中间设备通常更难更新,有时对它们接受的内容更加严格。例如,如果设备是防火墙,它可能被配置为阻止所有包含(未知)扩展的流量。实践中,结果发现大量的活跃中间设备对 TCP 进行了某些假设,新的扩展不再适用。
因此,可能需要数年甚至十多年的时间,才能更新足够多的(中间设备)TCP 实现,以在大规模上实际使用这些扩展。你可以说,演进 TCP 已经变得实际上不可能了。
结果表明,我们需要一个取代 TCP 的替代协议,而不是直接升级,以解决这些问题。然而,由于 TCP 的功能复杂性及其各种实现,从零开始创建一些新的但更好的东西将是一个巨大的任务。因此,在 2010 年代初,决定推迟这项工作。
毕竟,不仅仅是 TCP 存在问题,HTTP/1.1也有问题。我们选择分拆工作,首先“修复”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)。这是真的,但并非出于许多人声称的(性能)原因。理想情况下,QUIC 将作为一个完全独立的新传输协议,直接在上面面提到的图像中的 IP 之上运行。
然而,这样做将导致我们在尝试演进 TCP 时遇到的同样问题:所有设备都必须首先更新,以便识别并允许 QUIC。幸运的是,我们可以在互联网上另一种广泛支持的传输层协议之上构建 QUIC:UDP。
你知道吗?
UDP 是最基本的传输协议。它真的不提供任何功能,除了所谓的端口号(例如,HTTP 使用端口 80,HTTPS 位于443,DNS 使用端口 53)。它不通过握手建立连接,也不可靠:如果 UDP 数据包丢失,它不会自动重传。因此,UDP 的“尽力而为”方法意味着它的性能非常高:不需要等待握手,没有 HoL 阻塞。实践中,UDP 主要用于实时流量,该流量以高速率更新,因此数据包丢失后,缺失的数据很快就会过时(例如,实时视频会议和游戏)。它也适用于需要低前期延迟的情况;例如,DNS 域名查找真的应该只需要一个往返就能完成。
许多来源声称 HTTP/3 是基于 UDP 构建的,因为性能原因。他们说 HTTP/3 更快,因为就像 UDP 一样,它不建立连接,也不等待数据包重传。这些说法是错误的。正如我们上面所说,我们主要希望借助 UDP 使用 QUIC(因此,HTTP/3),因为希望它们能更容易部署,因为(几乎)所有互联网设备都已知道并实现了 UDP。
然后,在 UDP 之上,QUIC 实质上重新实现了几乎所有使 TCP 成为如此强大和流行(尽管有些慢)协议的特性。QUIC 绝对可靠,使用接收到的数据包的确认和重传来确保丢失的数据包仍然到达。QUIC 还建立了连接,并有一个高度复杂的握手。
最后,QUIC 还使用所谓的流量控制和拥塞控制机制来防止发送者过载网络或接收者,但这也使 TCP 比使用原始 UDP 的速度慢。关键是 QUIC 以比 TCP 更智能、更高性能的方式实现了这些功能。它结合了几十年的 TCP 部署经验和最佳实践以及一些核心新特性。我们将在本文后面更深入地讨论这些特性。
关键点
这里的关键点是,不存在免费午餐。HTTP/3 并不是因为我们将 TCP 替换为 UDP 就神奇地比 HTTP/2 更快。相反,我们重新想象并实现了一个更先进的 TCP 版本,并将其命名为 QUIC。因为我们希望使 QUIC 更容易部署,所以我们在 UDP 之上运行它。
主要变化
那么,QUIC 是如何改进 TCP 的呢?有什么不同呢?QUIC 有几个新的具体特性和机会(0-RTT 数据、连接迁移、对丢包和慢速网络的更大韧性),我们将在系列的下一部分详细讨论。然而,所有这些新东西基本上都归结为四个主要变化:
- QUIC 与 TLS 深度集成。
- QUIC 支持多个独立字节流。
- QUIC 使用连接 ID。
- QUIC 使用帧。
让我们仔细看看这些点。
没有TLS的QUIC不存在
如前所述,TLS(传输层安全性协议)负责加密和保护通过互联网发送的数据。当你使用 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。
因此,它在一定程度上打破了我们在之前图片中看到的协议栈中协议之间典型的清晰分离。虽然 TLS 1.3 仍然可以独立地在 TCP 之上运行,QUIC 相反地某种程度上封装了 TLS 1.3。换句话说,没有办法不使用 TLS 使用 QUIC;QUIC(以及随之 HTTP/3)始终是完全加密的。此外,QUIC 还加密了几乎所有的数据包头部字段;传输层信息(例如,TCP 从不加密的数据包号)在 QUIC 中不再为中间者可读(甚至一些数据包头部标志也被加密)。
通过以上所有设置,QUIC 为用户提供了几个好处:
- QUIC 对其用户来说更安全。
没有办法运行明文 QUIC,因此攻击者和窃听者的监听选项也更少。(最近的研究已经显示出 HTTP/2 的明文选项有多么危险。) - QUIC 的连接设置更快。
虽然 TLS-over-TCP 需要各自的不同握手,QUIC 相反地将传输和加密握手合为一体,节省了一个往返(见上图)。我们将在第2部分中更详细地讨论这一点。 - QUIC 可以更容易地演进。
因为它几乎完全加密,网络中的中间设备不再能像它们可以对 TCP 那样观察和解释其内部工作。因此,它们也不再能够在新版本的 QUIC 中因未更新而(意外)中断。如果将来我们想要为 QUIC 添加新特性,我们“只”需要更新端点,而不需要更新所有中间设备。
除了这些优点,广泛加密也有一些潜在的缺点:
- 许多网络将犹豫是否允许 QUIC。
公司可能希望在其防火墙上阻止它,因为检测不需要的流量变得更加困难。ISP 和中间网络可能会阻止它,因为平均延迟和数据包丢失百分比等指标不再容易获得,使得检测和诊断问题更加困难。这一切意味着 QUIC 可能永远不会被普遍接受,我们将在第3部分中更多地讨论这一点。 - QUIC 有更高的加密开销。
QUIC 使用 TLS 对每个单独的数据包进行加密,而 TLS-over-TCP 可以同时加密多个数据包。这可能使得 QUIC 在高吞吐量场景下速度更慢(如我们将在第2部分中看到)。 - QUIC 使得网络更加集中化。
我经常遇到的一个抱怨是类似的,“Google 在推动 QUIC,因为它给了他们完全访问数据的权限,而不与其他人共享”。我大多不同意这一点。首先,QUIC 不会比 TLS-over-TC P更多(或更少!)地向外部观察者隐藏用户级信息(例如,你正在访问的 URL)。其次,虽然 Google 启动了 QUIC 项目,但我们今天讨论的最终协议是由互联网工程任务组(IETF)中更广泛的团队设计的。IETF 的 QUIC 在技术上与 Google 的 QUIC 非常不同。不过,这个团队确实主要由像 Google 和 Facebook 这样的大公司以及像 Cloudflare 和 Fastly 这样的 CDN 的人组成。由于 QUIC 的复杂性,主要是这些公司才拥有正确和高效部署 HTTP/3 的必要知识。这可能会导致这些公司的集中化程度更高,这是一个真正的担忧。
个人观点:
这就是我写这类文章和做很多技术演讲的原因之一:确保更多的人了解协议的细节,并可以独立于这些大公司使用它们。
关键点
这里的关键点是,QUIC 默认深度加密。这不仅改善了其安全性和隐私特性,还有助于其部署和演进。它使得协议运行起来有点重,但作为回报,允许其他优化,如更快的连接建立。
QUIC了解多字节流
TCP 与 QUIC 之间的第二个主要区别有些技术性,我们将在第2部分中更详细地探讨其影响。不过,现在,我们可以从高层次上理解主要方面。
你知道吗?
首先考虑即使是一个简单的网页也是由许多独立的文件和资源组成的。有 HTML、CSS、JavaScript、图像等等。这些文件中的每一个都可以被视为一个简单的“二进制 blob”——一个由浏览器以某种方式解释的零和一的集合。当我们通过网络发送这些文件时,我们不会一次性传输它们,而是将它们细分为更小的块(通常约 1400 字节)并在单独的数据包中发送。因此,我们可以将每个资源视为一个单独的“字节流”,因为数据随着时间的推移以零散的方式下载或“流式传输”。
对于 HTTP/1.1,资源加载过程非常简单,因为每个文件都有自己的 TCP连接并完整下载。例如,如果我们有文件A、B和C,我们会有三个 TCP 连接。第一个会看到字节流 AAAA,第二个 BBBB,第三个 CCCC(每个字母重复表示一个 TCP 数据包)。这个工作,但也非常低效,因为每个新连接都有一些开销。
实践中,浏览器对当前可以使用的并发连接数(因此,可以并行下载的文件数)施加了限制——通常每个页面加载限制在 6 到 30 之间。然后重用连接来下载前一个完全传输的新文件。对于通常加载远多于 30 个资源的现代页面,这些限制最终开始阻碍了 Web 性能。
改善这种情况是 HTTP/2 的主要目标之一。协议通过不再为每个文件打开一个新的 TCP 连接,而是通过单个 TCP 连接下载不同的资源来实现这一点。这是通过 “多路复用”不同字节流 来实现的。这是说我们在传输数据时混合了不同文件的数据的一种花哨的方式。对于我们的三个示例文件,我们将得到一个 TCP 连接,传入数据可能看起来像AABBCCAABBCC(尽管许多其他排序方案是可能的)。这似乎很简单,并且确实工作得相当好,使得 HTTP/2 通常和 HTTP/1.1 一样快或略快,但开销要少得多。
让我们来看看两者的区别:
然而,TCP 这边存在一个问题。你看,由于 TCP 是一个更老的协议且并非仅仅为了加载网页而设计的,它不知道 A、B或 C。在内部,TCP认为它正在传输的是单个文件,X,并且不关心它在 HTTP 层面被视为 AABBCCAABBCC 的事实。在大多数情况下,这不重要(这实际上使得 TCP 相当灵活!),但当例如网络上出现数据包丢失时,就会改变。
假设第三个 TCP 数据包丢失了(包含文件 B 的第一批数据),但所有其他数据都被送达。TCP 通过重传丢失数据的新副本在新数据包中处理这个丢失。然而,这次重传可能需要一段时间才能到达(至少一个 RTT)。你可能会认为这不是一个大问题,因为我们看到资源 A 和 C 没有丢失。因此,我们可以在等待 B 丢失的数据时开始处理 A 和 C ,对吗?
遗憾的是,情况并非如此,因为重传逻辑发生在 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部分中讨论。
我们可以看到 TC P和 QUIC 之间有了一个根本的区别。这也是为什么我们不能直接在 QUIC 之上运行 HTTP/2 的一个主要原因。正如我们所说,HTTP/2 也包括在单个(TCP)连接上运行多个流的概念。因此,HTTP/2-over-QUIC将在其上拥有两个不同且相互竞争的流抽象。
使它们很好地协同工作将非常复杂且容易出错;因此,HTTP/2 和 HTTP/3 之间的一个关键区别是,后者移除了 HTTP 流逻辑并重用了 QUIC 流。如我们将在第2部分中看到的那样,这在如何实现服务器推送、头部压缩和优先级等特性方面有其他影响。
关键点
这里的关键点是 TCP 从未设计为通过单个连接传输多个独立文件。因为这正是 Web 浏览所需,所以这多年来导致了许多低效率。QUIC 通过在传输层将多个字节流作为核心概念,并在每个流基础上处理数据包丢失,解决了这个问题。
QUIC 支持连接迁移
QUIC 的第三个主要改进是连接可以保持更长时间活跃的事实。
你知道吗?
我们在讨论 Web 协议时经常使用“连接”这个概念。然而,“连接”到底是什么?通常,人们在两个端点(例如浏览器或客户端和服务器)之间完成握手后,会说有一个 TCP 连接。这就是为什么 UDP 经常(有些误导性地)被说成是“无连接”的,因为它不进行这样的握手。然而,握手真的没什么特别的:它只是发送和接收一些具有特定格式的数据包。其目标之一,主要是确保另一端存在,并愿意且能够与我们通信。这里值得重复提及的是,尽管 QUIC 运行在 UDP 之上,但它也进行握手,后者本身并不实现。
那么,这些数据包是如何到达正确的目的地的?在互联网上,使用 IP 地址在两个独特的机器之间路由数据包。然而,仅有你的手机和服务器的 IP 地址是不够的,因为双方希望同时运行多个网络程序。
这就是为什么每个独立连接还被分配了一个端口号,以区分连接和它们所属的应用程序。服务器应用程序通常有一个根据其功能固定的端口号(例如 80 和 443 端口用于 HTTP(S),53端口用于 DNS),而客户端通常为每个连接选择它们的端口号(半)随机。
因此,要跨机器和应用程序定义唯一的连接,我们需要这四样所谓的4元组:客户端IP地址 + 客户端端口 + 服务器IP地址 + 服务器端口。
在 TCP 中,连接由 4 元组唯一标识。所以,如果这四个参数中的任何一个发生变化,连接将变得无效,需要重新建立(包括一个新的握手)。为了理解这一点,请想象停车场问题:你当前正在使用 Wi-Fi 的建筑物内使用智能手机。因此,你在这个 Wi-Fi 网络上有一个 IP 地址。
如果你现在走到外面,你的手机可能会切换到蜂窝 4G 网络。因为这是一个新网络,它将获得一个全新的 IP 地址,因为这些地址是网络特定的。现在,服务器将看到来自之前没见过的客户端 IP 的 TCP 数据包(虽然两个端口和服务器 IP 当然可以保持不变)。如下图所示。
但服务器怎么知道这些来自新 IP 的数据包属于“连接”?它怎么知道这些数据包不是属于蜂窝网络中另一个客户端的新连接,该客户端选择了相同的(随机的)客户端端口(这很容易发生)?遗憾的是,它无法知道这一点。
因为 TCP 在我们甚至梦想蜂窝网络和智能手机之前就被发明了,例如,没有机制允许客户端让服务器知道它已更改 IP。甚至没有方法来“关闭”连接,因为发送到旧 4 元组的 TCP 重置或结束命令甚至不会再到达客户端。因此,实际上,每次网络变化意味着现有的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 在跨网络时应该保持不变吗?好吧,那是一个简化。实际发生的是,客户端和服务器在 QUIC 中以完全加密的方式协商了一个共同的(随机生成的)CID 列表,所有这些实际上都映射到同一概念的“连接”。
例如,他们都知道 CIDs 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 协议之上运行的(大多数网络设备也支持),并且为了确保它将来可以演进,它几乎完全默认加密,并使用灵活的帧机制。
除此之外,QUIC 大部分模仿了已知的 TCP 特性,如握手、可靠性和拥塞控制。除了加密和帧之外的两个主要变化是意识到多个字节流和引入连接ID。这些变化足以防止我们直接在 QUIC 之上运行 HTTP/2,从而需要创建 HTTP/3(实际上是 HTTP/2-over-QUIC 的内部实现)。
QUIC 的新方法为一系列性能改进铺平了道路,但它们的潜在收益比通常在 QUIC 和 HTTP/3 的文章中传达的更为细微。现在我们知道了基本知识,我们可以在本系列的下一部分中更深入地讨论这些细微之处。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。