查看原文
其他

【第3197期】HTTP/3 简介:核心概念

桃猿 前端早读课 2024-03-09

前言

本文介绍了新的 HTTP/3 协议的核心概念,解释了其与 HTTP/2 的关系和发展背景。作者指出了在部署和配置 HTTP/3 时可能遇到的挑战,并强调了对于 Web 开发人员来说,了解协议细节的重要性。文章提出了对于 HTTP/3 的期望应保持理性,避免过度夸大其在 Web 性能提升方面的作用。今日前端早读课文章由 @桃猿翻译分享,公号:桃猿授权。

@桃猿:目前在 shopeepay 担任前端工程师,专注于支付领域的业务。

正文从这开始~~

经过近五年的发展,新的 HTTP/3 协议即将成型。早期版本已作为实验性功能可用,但您可以预期在 2021 年逐渐推出 HTTP/3 并正式使用。那么 HTTP/3 到底是什么?为什么在 HTTP/2 之后这么快就需要它?您应该如何使用它?特别是,它如何提升 Web 性能?让我们一探究竟。

您可能已经阅读了一些博客文章或听过会议演讲,认为自己已经了解了答案。您可能听说过一些观点,比如:“在存在数据包丢失时,HTTP/3 比 HTTP/2 快得多”,或者 “HTTP/3 连接具有更低的延迟,建立连接的时间更短”,可能还有 “HTTP/3 可以更快地发送数据,可以并行发送更多资源”。

【图书】图解TCP/IP(第6版)

这些陈述和文章通常忽略了一些关键的技术细节,缺乏细致入微的描述,并且通常只是部分正确。它们经常让人觉得 HTTP/3 是性能革命,而实际上它只是一个更为温和(但仍然实用)的演进。这很危险,因为新协议在实践中可能无法达到这些高期望。我担心这会导致许多人感到失望,新手被大量盲目传播的错误信息弄得困惑。

我之所以担心,是因为我们在 HTTP/2 时期看到了完全相同的情况。它被宣扬为惊人的性能革命,带有令人兴奋的新功能,如服务器推送、并行流和优先级。我们本应该能够停止捆绑资源,停止将资源分散到多个服务器上,并大幅简化页面加载过程。只需轻松操作,网站就会神奇地变得快 50%!

五年后,我们知道服务器推送在实践中并不起作用,流和优先级经常实现不善,因此(减少的)资源捆绑甚至分片在某些情况下仍然是最佳实践。

类似地,调整协议行为的其他机制,如「预加载提示」,通常包含隐藏的深意和 bugs,使它们难以正确使用。

如此,我觉得很重要的是去阻止这种错误信息和这些不切实际的期望在 HTTP/3 的传播中滋生。

在这个系列文章中,我将更加细致入微地讨论新协议,特别是其性能特性。我将展示,虽然 HTTP/3 确实具有一些有前途的新概念,但遗憾的是,它们对大多数网页和用户的影响可能相对有限(但对于一小部分可能至关重要)。配置新协议时,HTTP/3 也相当具有挑战性,故在配置时请小心。

【第2651期】HTTP史记 - 从HTTP/1到HTTP/3

1\ HTTP/3 简介与核心概念

这篇文章面向对 HTTP/3 和协议一无所知的人,主要讨论基础知识。

2\ HTTP/3 性能特性深度剖析

这篇更深入、更技术性。已经了解基础知识的人可以从这里开始。

3\ 实用的 HTTP/3 部署选项

解释了部署和测试 HTTP/3 的挑战。它详细介绍了您是否应该以及如何更改您的网页和资源。

注意:本系列主要面向不一定深入了解协议的 Web 开发人员,但它包含足够的技术细节和许多外部链接,也适合更高级的读者。

为什么需要 HTTP/3?

经常有人问的一个问题是,“为什么我们在 HTTP/2 刚在 2015 年标准化后不久就需要 HTTP/3 呢?” 这确实很奇怪,直到你意识到我们首先并不真的需要一个新的 HTTP 版本,更需要的是对底层的传输控制协议(TCP)进行升级。

TCP 是提供关键服务,如可靠性和有序传递给其他协议(如 HTTP)的主要协议。它也是我们可以在许多并发用户中继续使用互联网的原因之一,因为它巧妙地将每个用户的带宽使用限制为相对公平的份额。

你知道吗?

在使用 HTTP (S) 时,实际上同时使用了除 HTTP 外的多个协议。这个 “堆栈” 中的每个协议都有自己的特性和责任(参见下图)。例如,HTTP 处理 URL 和数据解释,传输层安全性(TLS)通过加密确保安全性,TCP 通过重传丢失的数据包实现可靠数据传输,而 Internet 协议(IP)在中间设备之间路由数据包从一个端点到另一个端点。

协议的这种 “层叠” 使它们的特性易于重用。较高层的协议(如 HTTP)不必重新实现复杂的特性(如 加密),因为较低层的协议(如 TLS)已经为它们完成了。再举一个例子,互联网上的大多数应用程序都在内部使用 TCP 以确保它们的所有数据都完全传输。由此,TCP 成为了互联网上使用最广泛且部署最多的协议之一。

HTTP/2 与 HTTP/3 协议栈比较

TCP 在 Web 中担任角色数十年,但在 2000 年代末开始显老。它的预期替代品是一种名为 QUIC 的新传输协议,它与 TCP 在一些关键方面有足够多的不同,以至于直接在其上运行 HTTP/2 将非常困难。如此,HTTP/3 本身是对 HTTP/2 的相对小的调整,以使其与新的 QUIC 协议兼容,其中包括大多数人们感兴趣的新功能。

QUIC 的必要性来之于 TCP,它自互联网诞生之初就已经存在,并没有真正考虑到最大效率。例如,TCP 需要 “握手” 以建立新连接。这是为了确保客户端和服务器都存在,并且它们愿意且能够交换数据。然而,它需要完成一个完整的网络往返以前,连接上不能进行任何其他操作。如果客户端和服务器地理位置相距较远,每个往返时间(RTT)可能超过 100 毫秒,引起明显的延迟。

再举第二个例子,TCP 将其传输的所有数据视为单个 “文件” 或字节流,即使实际上我们正在使用它同时传输多个文件(例如,下载由许多资源组成的网页时)。在实践中,这意味着如果包含单个文件数据的 TCP 数据包丢失,那么所有其他文件也会延迟,直到这些数据包被恢复。

这被称为 “队头阻塞”(HoL blocking)。虽然这些效率低下在实践中相当可控(否则,我们在过去 30 多年中就不会一直使用 TCP 了),但它们确实以一种可察觉的方式影响了诸如 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 的主要期望功能(更快的连接设置、较少的排头阻塞、连接迁移等)实际上都来自于 QUIC。

什么是 QUIC?

你可能会想这有啥值得关注的?谁会在乎特性实现在 HTTP/3 还是 QUIC 中?但我感觉这很重要,因为 QUIC 是一种通用的传输协议,类似于 TCP,可以并将被用于许多用例,除了 HTTP 和网页加载之外,例如 DNS、SSH、SMB、RTP 等都可以在 QUIC 上运行。如此,让我们更深入地了解一下 QUIC,因为大部分我读过的关于 HTTP/3 的误解都来源于这里。

你可能听说过 QUIC 运行在另一种协议上,称为用户数据报协议(UDP)。这是真的,但并不是因为许多人声称的(性能)原因。理想情况下,QUIC 本应是一种全新的独立传输协议,在我分享的协议栈图中直接运行在 IP 之上。

然而,这样做会导致与尝试演进 TCP 时遇到的相同问题:为了识别和允许 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 之上,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 和 QUIC 握手持续时间

在互联网的早期阶段,加密流量在处理方面相当昂贵。此外,对于所有用例,加密流量也不被认为是必需的。历史上,TLS 因此一直是一个完全独立的协议,可以选择性地在 TCP 之上使用。这就是为什么我们在 HTTP(无 TLS)和 HTTPS(有 TLS)之间有一个区别的原因。

随着时间的推移,我们对互联网安全的态度当然发生了变化,变为 “默认安全”。如此,虽然 HTTP/2 理论上可以直接在没有 TLS 的情况下在 TCP 上运行(甚至在 RFC 规范中定义了明文 HTTP/2),但(流行的)Web 浏览器实际上不支持这种模式。在某种程度上,浏览器供应商在安全性和性能之间做出了有意识的权衡。

考虑到这种明显的向始终使用 TLS(特别是对于 Web 流量)的演变,不足为奇 QUIC 的设计者决定将这一趋势推向了一个新的水平。他们选择将加密深入到 QUIC 本身中,而不是简单地不为 HTTP/3 定义明文模式。虽然最初的 Google 特定版本的 QUIC 使用了自定义设置来实现这一点,但标准化的 QUIC 直接使用现有的 TLS 1.3 本身。

为此,它在协议栈中打破了典型的协议栈清晰分离,正如我们在前面的图像中看到的那样。虽然 TLS 1.3 仍然可以独立在 TCP 之上运行,但 QUIC 则将 TLS 1.3 进行了封装。换句话说,没有不使用 TLS 的 QUIC 的方法;QUIC(以及由此延伸的 HTTP/3)始终完全加密。此外,QUIC 几乎将其所有数据包头字段加密;传输层信息(例如,对于 TCP 永远不会加密的数据包编号)在 QUIC 中不再可读(甚至某些数据包头标志也已加密)。

与 TCP + TLS 不同,QUIC 还加密了其数据包头和有效负载中的传输层元数据。(注意:字段大小不成比例)

为了实现所有这些,QUIC 首先使用 TLS 1.3 握手来建立数学加密参数,就像您在 TCP 中所做的那样。之后,QUIC 接管并自行加密数据包,而对于 TLS-over-TCP,TLS 会执行自己的加密。这个看似微小的区别代表了向更低的协议层强制始终启用加密的根本概念性变化。

这种方法为 QUIC 带来了几个优势:

  • 更安全的用户体验

无法运行明文 QUIC,减少攻击者和窃听者监听的选项(最近的研究表明 HTTP/2 的明文选项有多么危险)。

  • 更快的连接建立

与 TLS-over-TCP 不同,QUIC 将传输和加密握手合并为一个,节省了一个往返的时间,我们将在第二部分的文章中讨论这个。

  • 更容易演进

完全加密,网络中的中间盒无法观察和解释其内部工作原理,与 TCP 不同。因此,加密也不会再因为更新失败而在新版本的 QUIC 中(意外地)中断。在未来要向 QUIC 添加新功能时,“只需” 更新终端设备,而不是所有中间盒。

然而,除了这些优势外,广泛加密也可能带来一些潜在的问题:

  • 许多网络可能不愿允许 QUIC

公司可能希望在其防火墙上阻止 QUIC,因为检测不需要的流量变得更加困难。ISP 和中间网络可能会阻止它,因为平均延迟和数据包丢失百分比等指标不再容易获得,从而使检测和诊断问题变得更加困难。这一切都意味着 QUIC 可能永远不会普遍可用,我们将在第三部分中对此进行更多讨论。

  • 更高的加密开销

QUIC 使用 TLS 为每个数据包加密,而 TLS-over-TCP 可以同时加密多个数据包。这可能使 QUIC 在高吞吐量场景下变慢(正如我们将在第 2 部分中看到的)。

  • 使 Web 更加集中

我经常遇到的抱怨是这样的:“Google 正在推动 QUIC,因为它使他们能够完全访问数据,同时不与其他人共享任何数据”。我不大同意这一点。首先,与 TLS-over-TCP 相比,QUIC 不会向外部观察者隐藏更多(或更少!)的用户级信息(例如,您正在访问哪些 URL)(QUIC 保持现状)。

其次,虽然谷歌发起了 QUIC 项目,但我们今天讨论的最终协议是由互联网工程任务组 (IETF) 中更广泛的团队设计的。IETF 的 QUIC 在技术上与 Google 的 QUIC 有很大不同。尽管如此,IETF 的成员确实大多来自 Google 和 Facebook 等大公司以及 Cloudflare 和 Fastly 等 CDN。由于 QUIC 的复杂性,主要是那些拥有正确、高效地部署必要知识的公司,例如实践中的 HTTP/3。这可能会导致这些公司更加集中,这是一个真正令人担忧的问题。

个人说明:

这是我撰写此类文章并进行大量技术讲座的原因之一:确保更多的人了解协议的细节,并能够独立于这些大公司使用它们。

要点

QUIC 默认深度加密,这不仅提高了其安全性和隐私特性,还有助于其可部署性和可演进性。虽然使协议运行变得更重,但却允许其他优化,如更快的连接建立。

QUIC 理解多个字节流

TCP 和 QUIC 之间的第二个重要区别略微技术化,我们将在第二部分详细探讨其影响。不过,目前我们可以以高层次的方式理解主要方面。

你知道吗?

首先考虑一个简单的网页,由许多独立的文件和资源组成。有 HTML、CSS、JavaScript、图像等。每个文件都可以看作是一个简单的 “二进制块” —— 一组由浏览器以某种方式解释的零和一。在通过网络发送这些文件时,我们不会一次性传输它们。相反,它们被细分为较小的块(通常约 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 一样快或稍快,但开销要少得多。

让我们更近距离地看看这种区别:

HTTP/1.1 不支持多路复用,与 HTTP/2 和 HTTP/3 不同

然而,在 TCP 方面存在问题。因为 TCP 是一个更古老的协议,不仅仅用于加载网页,它并不了解 A、B 或 C。在 TCP 内部,它认为自己传输的只是一个单一的文件,X,并且它并不关心在 HTTP 层面上视为 AABBCCAABBCC 的 XXXXXXXXXXXX 实际上是什么。在大多数情况下,这并不重要(实际上这使 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 层。(如下图所示。)理论上,这可能导致性能改进。然而,在实践中,情况要复杂得多,我们将在第二部分讨论。

QUIC 允许 HTTP/3 绕过头部阻塞问题。

我们现在可以看到 TCP 和 QUIC 之间有根本的差异。顺便说一下,这也是为什么我们不能简单地在 QUIC 上运行 HTTP/2 的主要原因之一。正如我们所说,HTTP/2 还包括在单一(TCP)连接上运行多个流的概念。如此,通过 QUIC 运行的 HTTP/2 会在彼此之上有两种不同且竞争的流抽象。

使它们良好协同工作将会非常复杂且容易出错;因此,HTTP/2 和 HTTP/3 之间的一个关键区别是后者移除了 HTTP 流逻辑,并重新使用 QUIC 流替代。然而,正如我们将在第二部分中看到的,这对服务器推送、头部压缩和优先级等功能的实现产生了其他影响。

要点

这里的关键要点是,TCP 从未被设计用于在单个连接上传输多个独立的文件。由于这正是 Web 浏览所需的,多年来产生了许多低效之处。QUIC 通过将多个字节流作为传输层的核心概念,并在每个流上处理数据包丢失,解决了这个问题。

QUIC 支持连接迁移

第三个主要改进是 QUIC 能够保持连接更长时间。

你知道吗?

在谈论网络协议时,我们经常使用 “连接” 这个概念。但是,究竟什么是连接呢?通常,人们在两个端点之间进行握手后(比如,浏览器或客户端与服务器之间),才谈论 TCP 连接。这就是为什么 UDP 经常(有点误导地)被说成 “无连接”,因为它不进行这样的握手。然而,握手并不是什么特别的事情:它只是发送和接收一些特定形式的数据包。握手有一些目标,其中主要目标之一是确保另一端存在并且愿意与我们通信。值得在这里重申的是,尽管 QUIC 在 UDP 上运行,它也执行握手。

因此,问题就是这些数据包如何到达正确的目的地?在互联网上,IP 地址用于在两台唯一的计算机之间路由数据包。然而,仅仅拥有手机和服务器的 IP 是不够的,因为两者都希望能够同时运行多个网络程序。

这就是为什么每个独立的连接在两个端点上也分配一个端口号,以区分连接和它们所属的应用程序。服务器应用程序通常根据其功能具有固定的端口号(例如 HTTP(S)的端口 80 和 443,DNS 的端口 53),而客户端通常为每个连接选择它们的端口号(半)随机地。

如此,为了定义跨计算机和应用程序的唯一连接,我们需要这四个东西,即所谓的 4 元组:客户端 IP 地址 + 客户端端口 + 服务器 IP 地址 + 服务器端口。

在 TCP 中,连接仅由 4 元组标识。因此,如果这四个参数中的一个发生变化,连接将变得无效,需要重新建立(包括新的握手)。要理解这一点,可以想象一下停车场问题:你目前正在建筑物内使用你的智能手机,如此,你有一个 ip 地址连接到了 Wi-Fi 网络。

如果你现在走到室外,你的手机可能会切换到蜂窝 4G 网络。因为这是一个新的网络,它将获得一个完全新的 IP 地址,因为这些地址是特定于网络的。现在,服务器将看到来自一个以前未见过的客户端 IP 的 TCP 数据包(尽管两个端口和服务器 IP 可能保持不变)。下图说明了这一点。

TCP 存在一个问题:一旦客户端获取新 IP,服务器无法将其与连接关联。

但是服务器如何知道来自新 IP 的数据包的所属 “连接” 呢?它如何知道这些数据包不属于在同一蜂窝网络中选用相同(随机)客户端端口的另一个客户端的新连接(这很容易发生)?遗憾的是,它无法知道。

因为 TCP 是在我们甚至还没有梦想到蜂窝网络和智能手机的时候发明的,所以,例如,没有机制允许客户端告诉服务器它已更改 IP。甚至没有一种 “关闭” 连接的方式,因为发送到旧的 4-tuple 的 TCP 重置或 fin 命令甚至不会再到达客户端。如此,实际上,每次网络更改意味着现有的 TCP 连接无法再使用。

必须执行新的 TCP(可能还有 TLS)握手来建立新连接,并且根据应用层协议,可能需要重新启动进程中的操作。例如,如果您正在通过 HTTP 下载大文件,那么可能需要重新请求该文件(例如,如果服务器不支持范围请求)。另一个例子是实时视频会议,在切换网络时可能会有短暂的中断。

请注意,4-tuple 可能发生变化的其他原因(例如,NAT 重新绑定),我们将在第二部分中更详细讨论。

重新启动 TCP 连接可能会产生严重影响(等待新的握手,重新启动下载,重新建立上下文)。为了解决这个问题,QUIC 引入了一个名为连接标识符(CID)的新概念。每个连接在 4-tuple 之上被分配另一个唯一标识它的号码,它在两个端点之间是唯一的。

关键是,因为这个 CID 在 QUIC 本身的传输层中定义,所以在移动网络之间移动时它不会改变!这在下面的图像中有所体现。为了使这成为可能,CID 被包含在每个 QUIC 数据包的前面(就像 IP 地址和端口也出现在每个数据包中一样)。(实际上,这是 QUIC 数据包头中少数几个未加密的部分之一!)

QUIC 使用连接标识符(CIDs)允许连接在网络更改时保持

通过这种设置,即使 4-tuple 中的某些内容发生变化,QUIC 服务器和客户端只需查看 CID 就知道它是同一个旧连接,然后它们可以继续使用它。不需要新的握手,下载状态可以保持完整。这个功能通常被称为连接迁移。理论上,这对性能更好,但正如我们将在第二部分中讨论的那样,这当然又是一个复杂的故事。

CID 还有其他挑战需要克服。例如,如果我们确实只使用一个 CID,那么黑客和监听者将极易跟踪用户跨网络,并由此推断出他们(大约的)物理位置。为了防止这种隐私噩梦,QUIC 每次使用新网络时都会更改 CID。

不过,这可能会让你感到困惑:我不是刚刚说 CID 应该在网络之间保持不变吗?嗯,这只是一个简略的说法。实际上内部发生的是,客户端和服务器会协商一个共同的 CID 列表(随机生成的),它们都映射到相同的概念 “连接”。

例如,它们都知道 CID K、C 和 D 实际上都映射到连接 X。如此,尽管客户端可能在 Wi-Fi 上标记数据包为 K,但它可以切换到 4G 上使用 C。这些共同的列表在 QUIC 中完全加密进行协商,因此潜在的攻击者不会知道 K 和 C 实际上是 X,但客户端和服务器会知道这一点,它们可以保持连接活动。

QUIC 使用多个经过协商的连接标识符(CIDs)来防止用户跟踪

它变得更加复杂,因为客户端和服务器将拥有它们自己选择的不同的 CID 列表(类似于它们拥有不同的端口号)。这主要是为了支持大规模服务器设置中的路由和负载平衡,我们将在第三部分中详细了解。

要点

TCP 中,连接由四个参数定义,当端点更改网络时,这些参数可能会更改,如此,有时需要重新启动这些连接,导致一些停机时间。QUIC 添加了另一个参数,称为连接标识符。QUIC 客户端和服务器都知道哪些连接标识符映射到哪些连接,故而更能弹性应对网络变化。

QUIC 灵活性与可演进

QUIC 有一个重要特点,即其被特意设计为易于演进。这通过几种不同的方式实现。首先,正如前面讨论的,QUIC 几乎完全加密,这意味着如果我们想部署 QUIC 的新版本,我们只需要更新端点(客户端和服务器),而无需更新所有中间框,尽管这仍需要时间,但通常是在几个月的范围内,而不是几年。

其次,与 TCP 不同,QUIC 不使用单一的固定数据包头部来发送所有协议元数据。相反,QUIC 具有短的数据包头部,并在数据包负载中使用各种 “帧”(类似于专用小型数据包)来传递额外的信息。例如,有一个 ACK 帧(用于确认),一个 NEW_CONNECTION_ID 帧(帮助设置连接迁移),以及一个 STREAM 帧(用于携带数据),如下图所示。

这主要是为了优化,因为并非每个数据包都携带所有可能的元数据(因此 TCP 数据包头部通常浪费了相当多的字节,也请参见上图)。然而,使用帧的一个非常有用的副作用是,在将来定义 QUIC 的新扩展时将会非常容易。其中一个非常重要的扩展是 DATAGRAM 帧,它允许在加密的 QUIC 连接上发送不可靠的数据。

QUIC 使用单独的帧来发送元数据,而不是大型固定数据包标头

第三,QUIC 使用自定义 TLS 扩展来携带所谓的传输参数。这允许客户端和服务器选择 QUIC 连接的配置。这意味着它们可以协商启用哪些功能(例如,是否允许连接迁移,支持哪些扩展等),并传达一些机制的合理默认值(例如,最大支持的数据包大小,流控制限制)。虽然 QUIC 标准定义了一长串这样的参数,但它也允许扩展定义新的参数,进一步使协议更加灵活。

最后,虽然这并不是 QUIC 本身的真正要求,但目前大多数实现都是在 “用户空间” 完成的(与通常在 “内核空间” 完成的 TCP 相对)。具体细节在第二部分中讨论,但这主要意味着与 TCP 相比,用 QUIC 实现变体和扩展的实验和部署要容易得多。

要点总结

尽管 QUIC 现在已经标准化,但实际上应该被视为 QUIC 版本 1(这也在请求评论(RFC)中明确说明),而且有明确的意图迅速创建版本 2 及更高版本。除此之外,QUIC 允许轻松定义扩展,因此可以实现更多的用例。

结论

让我们总结一下这部分学到的。我们主要讨论了无处不在的 TCP 协议,以及它是如何被设计于一个在当今的许多挑战都是未知的时代。在我们试图革新 TCP 时,实际上变得很困难,因为几乎每个设备都有自己的 TCP 实现,需要进行更新。

为了绕过这个问题,同时仍然改进 TCP,我们创建了新的 QUIC 协议(在底层实际上是 TCP 2.0)。为了更容易部署 QUIC,它运行在 UDP 协议的顶部(大多数网络设备也支持),为了确保它能够在未来演变,它默认几乎完全加密,并且使用了灵活的帧机制。

除此之外,QUIC 在很大程度上镜像了已知的 TCP 特性,如握手、可靠性和拥塞控制。除了加密和帧之外,两个主要变化是对多字节流的意识和引入连接标识。然而,这些变化足以阻止我们直接在 QUIC 上运行 HTTP/2,从而需要创建 HTTP/3(在底层实际上是 HTTP/2-over-QUIC)。

QUIC 的新方法带来了许多性能改进,但与通常在 QUIC 和 HTTP/3 的文章中传达的信息相比,其潜在收益更为微妙。现在我们了解了基础知识,我们可以在这个系列的下一部分更深入地讨论这些细微之处。

关于本文
译者:@桃猿
译文:https://mp.weixin.qq.com/s/eP4D0oP7AHcGeG4JUdEr8A
作者:@Robin Marx
原文:https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/

这期前端早读课
对你有帮助,帮” 
 “一下,
期待下一期,帮”
 在看” 一下 。

继续滑动看下一个

【第3197期】HTTP/3 简介:核心概念

向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存