HTTP2&3

HTTP2

简介

HTTP2 是一个应用层传输协议,它是 HTTP 协议的第二个版本。HTTP2 主要是基于 google 的 SPDY 协议,SPDY 的关键技术被 HTTP2 采纳了,因此 SPDY 的成员全程参与了 HTTP2 协议制定过程。多数浏览器在 2015 年底支持了该协议。

HTTP1.1不足

HTTP/1.0只允许每一个请求绑定到一个给定的连接上。 HTTP/1.1流水线只能部分地解决了并发的请求,并从线头的阻塞受到影响。 因此,需要进行多次请求客户端通常使用多个连接到服务器,以减少等待时间。

此外,HTTP/1.1的报头字段经常重复和冗长,其中,除了产生更多或更大的网络数据包,可能会导致小的初始TCP拥塞窗口来快速填充。当多个请求在一个新的TCP连接进行, 可能会导致过度的延迟。

Head-of-line blocking(HTTP头阻塞)

HTTP 1.1 利用 pipelining 可以同时在一个 TCP 中发送多个 HTTP 请求,但是客户端接收服务端响应信息时,还是按照发送时的顺序来接收响应信息。

这会导致一个什么问题呢?客户端接收时,如果第一个响应信息慢,会导致后面的响应信息阻塞。因为 http1.1 以前协议规定是一发一收这种模式,相当于一个先进先出的串行队列。服务器端为了按序返回响应信息也会占用很多服务器资源。

HTTP2 特性

  • 二进制分帧:HTTP2 中最小的传输单元叫做帧。HTTP2 定义了很多类型的帧,每个帧服务于不同的目的。例如 HEADERS 和 DATA 帧就构成了 HTTP 请求和应答的主体。还有其它的比如 WINDOW_UPDATE, PUSH_PROMISE 等帧类型用于支持 HTTP2 的其它特性。
  • 头部字段压缩:因为 HTTP 头包含了大量冗余数据,HTTP2 对这些数据进行了压缩,压缩后对于请求大小的影响显著,可以将多个请求压缩到一个包中。
  • 多路复用:每个 HTTP 请求/应答在各自的流(stream 也是 HTTP2 中的一个很重要概念)中完成数据交换。每个流都是相互独立。因此如果一个请求/应答阻塞或者速度很慢,也不会影响其它流中的请求/应答处理。在一个 TCP 连接中就可以传输多个流数据而无需建立多个连接。
  • 服务端推送
  • 流量控制和资源优先级
  • HTTP2 数据采用二进制编码,而不是原来的文本格式数据。

HTTP2 协议内容

HTTP2 协议概述

HTTP2 协议有两个标识符:

  • 字符串 "h2" 标识使用了 TLS 的 HTTP2 协议。该标识符用在 TLS-ALPNopen in new window 的扩展字段,以及其他需要标示运行于 TLS 之上 HTTP2 的地方。
  • 字符串 "h2c" 标识在构建在 TCP 之上的 HTTP2 协议,它是明文传输。该标识符用在 HTTP/1.1 的 Upgrade 首部字段,以及其他需要标示运行于 TCP 之上 HTTP/2 的地方。"h2c" 字符串保留在 ALPN extensionopen in new window 标识符空间,但是实际上标示了一个不使用 TLS 的协议。

HTTP 升级

客户端发起一个 http URI 请求时,使用 HTTP Upgrade 机制。客户端发起一个 HTTP1.1 请求,其中包含 "h2c" 的 Upgrade 首部字段,该请求还必须包含一个 HTTP2-Settings 首部字段。

例如:

CopyGET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
  • Connection 连接方式是升级协议
  • Upgrade 升级到什么协议,例子中是升级到 h2c

如果服务器不同意升级或者不支持 Upgrade 升级,可以直接忽略,当成是 HTTP1.1 请求和响应就好了。

如果服务器同意升级,响应格式为:

CopyHTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c

[ HTTP/2 connection ...

HTTP 响应升级的状态码是 101(Switching Protocols)。在结束 101 响应的空行后,服务端可以开始发送 HTTP2 数据帧了。

帧、消息、流

HTTP2 协议是由 HTTP1.1 升级而来,HTTP 的语义不变,提供的功能没有变化,HTTP方法、状态码、URI和Header字段这些都没有变化。在 HTTP2 中,传输数据时编码是不同的,与换行符分隔文本的 HTTP1.1 协议不同,HTTP2 中数据交换都被拆分为更小的消息和帧,而每个消息和帧都是用二进制格式来编码。帧是 HTTP2 中最小数据单元。

  • 帧 frame:HTTP2 中最小通信数据单元,每个帧至少包含了一个标识(stream identifier,简称stream id)该帧所属的流。
  • 消息 message:消息由一个或多个帧组成。例如请求的消息和响应的消息。
  • 流 stream:存在于 HTTP2 连接中的一个“虚拟连接通道“,它是一个逻辑概念。流可以承载双向字节流,及是客户端和服务端可以进行双向通信的字节序列。每个流都有一个唯一的整数 ID(stream identifier) 标识,由发起流的一端分配给流。单个 HTTP2 连接可以包含多个同时打开的流,任何一个端点(客户端和服务端)都可以将多个流的消息进行传输。这也是多路复用关键所在。一个 TCP 连接(HTTP2 连接建立在 TCP 连接之上)里可以发送若干个流(stream),每个流中可以传输若干条消息(message),每条消息由若干二进制帧(frame)组成。任何一端都可以关闭流。在流上发送消息的顺序很重要,最后接收端会把 Stream Identifier (同一个流) 相同的帧重新组装成完整的消息报文。特别是 HEADERS 帧和 DATA 帧的顺序在语义上非常重要。

HTTP2 中连接Connection、流Stream、消息Message、帧Frame的关系示意图如下:image-20221129021828289

二进制分帧层

image-20221129010912130

从上图可以看出,HTTP1.1 是明文文本,而 HTTP2.0 首部(HEADERS)和数据消息主体(DATA)都是帧(frame)。frame 是 HTTP2 协议中最小数据传输单元。

帧Frame的格式

一旦建立了 HTTP2 连接,端点(endpoints)间就可以开始交换帧数据。

所有的帧数据都是以一个固定的 9 字节开头(Frame Payload之前),后面跟一个可变长度的有效负载Frame Payload,这个可变长度的长度值由字段 Length 来表示。

帧的格式:

CopyHTTP Frame {
  Length (24),
  Type (8),
  Flags (8),
  Reserved (1),
  Stream Identifier (31),
  Frame Payload (..),
}

帧类型Type,HTTP2 中共分为 10 种类型:如0x00数据帧,0x01头帧,0x03流终止帧,0x05推送帧等等

重要特性详解

多路复用

在 HTTP1.1 中,一个 HTTP 的数据传输需要建立一个 TCP 连接,虽然有 Pipleing 特性,但是又有对头阻塞的问题。

在 HTTP2 中,在一个 TCP 连接中,可以发起多个 HTTP2 连接请求,而每个 HTTP2 连接中又可以发起多个流来传输数据。

image-20221129173045205

从图中可以看到在 HTTP1.1 中,请求 index.html 资源,响应完毕后就关闭连接了。而在 HTTP2 中,请求完资源后,连接仍然是打开的,后面还可以继续使用这个连接通道传输数据。

从 HTTP2 中 stream(流)角度来看看多路复用:

image-20221129173844784

头部压缩

HTTP 1.1 请求头的协议内容很多,而且大部分都是重复的。在 HTTP1.1 中每次请求都会大量携带这种冗余的头信息,浪费流量。

在 HTTP2 中,设计了 HPACK 压缩算法对头部协议内容进行压缩传输,这样不仅数据传输速度加快,也能节省网络流量。

HPACK 原理:

  • 客户端和服务端共同维护了一份静态字典表(Static Table),其中包含了常见头部名及常见头部名称与值的组合的代码。
  • 客户端和服务端根据先入先出的原则,共同维护了一份能动态添加内容的动态字典表(Dynamic Table)。
  • 客户端和服务端支持基于静态哈夫曼码表的哈夫曼编码(Huffman Coding)

image-20221129183818606

服务端推送

服务端推送是一种在客户端请求之前发送数据的机制。在 HTTP2 中,服务器可以对客户端的一个请求发送多个响应。除了对原始请求响应外,还可以向客户端推送额外的数据。

服务端推送的目的是让服务器通过预测它收到请求后有哪些相关资源需要返回,从而减少资源请求往返次数。

比如在 HTML 页面的请求后,通常是对该页面应用的样式表和脚本的请求,当这些资源被服务端直接推送给客户端时,客户端就不需要单独给服务器发送请求来获取这些资源了。

image-20221129210231148

在 page.html 文件中包含资源文件 script.js 和 style.css。客户端向服务端请求 page.html 文件,服务端发现 page.html 文件中包含了这两种资源文件,就会把这两种资源文件推送给客户端,以此来减少客户端的请求次数。

所有服务端推送数据流都由 PUSH_PROMISE 帧发起。

在实践中,服务端推送很难有效使用。因为需要服务端正确预测客户端发出的额外请求,预测必须同时考虑缓存、内容协商和用户行为等因素。预测错误又可能导致性能下降,因为服务端发送了额外的数据。特别是推送了大量数据时可能与重要的响应数据发生线路争用等问题。

客户端可以请求禁用服务端推送,SETTINGS_ENABLE_PUSHopen in new window 设置为 0 就可以了。

流优先级

在像 HTTP2 这样的使用了多路复用的协议中,为流分配带宽和计算资源的优先次序对于实现良好的性能至关重要。

在 HTTP2 中,一个消息可以拆分为多个单独的帧,并且允许来自多个流的帧被多路复用,客户端和服务端帧传输可能是乱序传输,所以优先级顺序就变成了一个关键性能考虑因素。还有一种重要情况是,当发送受到限制情况下,可以通过优先级顺序选择那个流传输帧。显示设置过优先级的流将被优先安排。但这种优先并不能保证一个优先级高的流能得到优先级处理或优先级传输。所以说,优先级仅仅作为一种建议存在。

HTTP2 允许每个流具有关联的权重和依赖性:

  • 每个流可以分配一个 1-256 范围之间整数权重
  • 每个流都可以被赋予对另外一个流的依赖性

比如html优先级最高,然后是js,css等,最后才是图片。

总结

高健壮性

HTTP/1.1,使用基于文本格式,文本表现形式多样、场景多,健壮性不足。HTTP/2使用二进制格式,只有0和1的组合,选择二进制传输,协议解析实现方便且健壮。

高性能

HTTP连接会随着时间进行自我调节,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调节被称为TCP慢启动。这种调节让具有突发性和短时性的HTTP连接变的十分低效。HTTP/2通过多路复用让所有数据流使用同一个连接,有效使用TCP连接,让高带宽也能真正的服务于HTTP的性能提升。

HTTP/2在应用层和传输层之间增加了二进制分帧,突破了HTTP/1.1性能限制,改进传输性能,实现低延迟和高吞吐量。

网络开销低

HTTP/2使用HPACK算法来压缩每次请求连接的头字段,降低了网络开销。HPACK算法可以减少需要传输的头字段大小,通讯双方通过建立和维护头字段表,字段表中使用长度较小的索引号表示重复的字符串,在用Huffman编码压缩数据,既避免了重复头字段的传输,又减小了需要传输的大小。

HTTP3

简介

HTTP/3是第三个主要版本的HTTP协议。与其前任HTTP/1.1和HTTP/2不同,在HTTP/3中,将弃用TCP协议,改为使用基于UDP协议的QUIC协议(快速的UDP网络连接)实现。

此变化主要为了解决HTTP/2中存在的队头阻塞问题。由于HTTP/2在单个TCP连接上使用了多路复用,受到TCP拥塞控制的影响,少量的丢包就可能导致整个TCP连接上的所有流被阻塞。

HTTP2不足:

HTTP/2虽然实现了多路复用,即在一个TCP连接上同时处理多个请求,但由于它是基于TCP协议的,当发生丢包时,TCP协议需要等待前面的包被成功接收后才会发送后面的包,这就导致了队头阻塞问题。

只要是TCP协议,就会有队头阻塞。

HTTP3实现

在这里插入图片描述

可以看到HTTP3基于QUIC和UDP,QUIC则实现了TCP还有TSL等的各项功能,确保了数据的可靠。

QUIC协议

内核空间 vs 用户空间

  • 内核空间:这是操作系统内核执行的地方,具有最高的权限,直接控制硬件资源。内核空间中的代码具有高效率和低延迟,但修改和调试非常复杂,且需要系统级权限。
  • 用户空间:这是应用程序运行的地方,权限受到限制,无法直接访问硬件资源。用户空间中的代码易于修改和调试,但其操作需要通过系统调用与内核通信,这会带来一定的性能开销。

TCP/IP 协议栈实现

传统的TCP/IP协议栈通常实现于内核空间。这意味着操作系统内核负责处理所有TCP/IP数据包的发送、接收、处理和路由。

优点:

  • 高效的网络数据包处理。
  • 可靠的内核级错误处理和拥塞控制。

缺点:

  • 修改和优化协议栈需要操作系统内核级别的更改,这很复杂且不易部署。
  • 开发和调试协议栈需要系统级权限,增加了开发难度。

QUIC的用户空间实现

QUIC协议栈在用户空间中实现。这意味着QUIC协议的所有逻辑处理都在应用程序级别完成,而不是在操作系统内核中。

优点:

  • 灵活性和可扩展性:因为QUIC协议在用户空间中实现,开发者可以更容易地修改、优化和扩展协议功能,而无需更改操作系统内核。这大大简化了协议的开发和部署过程。

  • 独立更新:由于在用户空间中实现,QUIC协议的更新和升级可以通过简单的软件更新来实现,而不需要操作系统的更新。这对于快速迭代和发布新版本尤为重要。

  • 应用级控制:应用程序可以更直接地控制和优化网络行为。例如,Google的BoringSSL库中实现了QUIC的加密和传输功能,能够更好地与应用需求相适应。

用户空间实现的挑战

  • 性能开销:由于需要在用户空间和内核空间之间进行系统调用,用户空间实现的网络协议可能面临性能开销。不过,QUIC通过UDP进行传输,避免了一些TCP的开销,并通过高效的用户空间实现技术(如零拷贝)来减小这些性能损失。

  • 安全性:用户空间代码的安全性往往不如内核空间代码。因此,QUIC协议需要特别注意安全机制的设计和实现,确保数据传输的安全性。(这也是为什么内置了TLS1.3的原因)

Last Updated:
Contributors: liushun-ing