# http2

# 二进制分帧

HTTP/2 为什么能在不改动 HTTP/1.x 的语义、方法、状态码以及首部字段等的情况下,改进传输性能,实现低延迟和高吞吐量?

关键之一就是在应用层(http2)和传输层(TCP or UDP)之间增加一个二进制分帧层。

┌───────────────────────────┐
│           HTTP2           │
└─────────────┬─────────────┘       ┌───────────────┐
┌─────────────┴─────────────┐       │  HEADER frame │
│        二进制分帧层         │─────> │───────────────│
└─────────────┬─────────────┘       │  DATA frame   │
┌─────────────┴─────────────┐       └───────────────┘
│            TCP            │
└───────────────────────────┘
1
2
3
4
5
6
7
8
9

在二进制分帧层中,http2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码。其中 http1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面

http1.x 诞生的时候是明文协议,基于文本协议的格式解析要做到健壮性考虑的场景必然很多。基于这种考虑 http2.0 的协议解析决定采用二进制格式,二进制只认 0 和 1 的组合。实现方便且健壮。

http1.x 信息组成: start line、header、body

http2 信息组成: length、type、flags、stream id、payload

# 帧(frame)和流(stream)

帧和流是 http2 的两个核心概念。先介绍几个概念:

Connection 连接: 1 个 TCP 连接,包含 1 个或者多个 stream。所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。

Stream 数据流:一个双向通信的数据流,包含 1 条或者多条 Message。每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。

Message 消息:对应 HTTP/1.1 中的请求 request 或者响应 response,包含 1 条或者多条 Frame。

Frame 数据帧:最小通信单位,以二进制压缩格式存放内容。来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。

# 帧(frame)

所有帧都以固定的 9 字节(72 位)大小的头作为帧开始,后跟可变长度的有效载荷 payload。以下为帧的结构,括号内容为位数。

┌───────────────────────────────┐
│         Length(24)            │
└─────────────┬─────────────────┘
┌─────────────┴─────────────────┐
│   Type(8)   │   Flags(8)      │
└─────────────┬─────────────────┘
┌─────────────┴─────────────────┐
│ R(1) │ Stream Identifier(31)  │
└─────────────┬─────────────────┘
┌─────────────┴─────────────────┐
│      Frame Payload(any)       │
└───────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12

各个字段含义:

  • Length

表示整个 frame 的长度,用一个 24 位无符号位表示,帧头的 9 个八位字节不包含在此长度值中。

但是这不意味着就能处理 2^24 大小的帧,除非接收方为 SETTINGS_MAX_FRAME_SIZE 设置了较大的值,否则不得发送大于 2^14(16,384)的值。

  • Type

定义 frame 的类型。帧类型决定了帧主体的格式和语义,如果 type 为 unknown 应该忽略或抛弃。

帧类型 编码类型 用途
DATA 0x0 传递HTTP包体
HEADERS 0x1 传递HTTP包头
PRIORITY 0x2 指定Stream 流的优先级
RST_STREAM 0x3 终止Stream流
SETTINGS 0x4 修改连接或者Stream流的配置
PUSH_PROMISE 0x5 服务端推送资源时描述请求的帧
PING 0x6 心跳监测兼具测量RTT的功能
GOAWAY 0x7 优雅的终止错误或通知错误
WINDOW_UPDATE 0x8 实现流量控制
CONTINUATION 0x9 传递较大HTTP头部时的持续帧
  • Flags

为帧类型相关而预留的布尔标识。标识对于不同的帧类型赋予了不同的语义。

  • R

是一个保留的比特位。这个比特的语义没有定义,发送时它必须被设置为 (0x0), 接收时需要忽略。

  • Stream Identifier

流标识符,表示为无符号 31 位整数,用于与整个连接相关联的帧。Stream Identifier 的作用:

  • 实现多路复用的关键。接收端的实现可以根据这个 ID 并发组装消息。

  • 推送依赖性请求的关键。客户端发起的流是奇数编号,服务端发起的流是偶数编号。

  • Payload

request 的正文

# 流(stream)

流可以看做是 http1.x 里面的每个请求,http2.0 会将每个流分成多个帧进行消息传输,同一个流所有的帧都有相同的 Stream Identifier 用于标识是哪个流,接收方通过帧的 Stream Identifier 将收到的帧组成一整块的流数据。

发送方可以为不同的流设置优先级,接收方会先处理优先级更高的流。

# 多路复用 (Multiplexing)

在过去, HTTP 性能优化的关键并不在于高带宽,而是低延迟。TCP 连接会随着时间进行自我「调谐」,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐则被称为 TCP 慢启动。

由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效。

http2.0 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升 ———— 只需要创建一个新流即可,这不需要多少时间。单连接有如下优势:

  1. 单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大
  2. 由于 TCP 连接的减少而使网络拥塞状况得以改善,同时慢启动时间的减少,使拥塞和丢包恢复速度更快

在 http1.x 协议中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(这也是为何一些站点会有多个静态资源 CDN 域名的原因之一),超过限制数目的请求会被阻塞。多路复用代替原来的序列和阻塞机制,所有就是请求的都是通过一个 TCP 连接并发完成。

# 首部压缩(Header Compression)

http1.x 的 header 由于 cookie 和 user agent 很容易膨胀,而且每次都要重复发送。

http2.0 采用了 HPACK 压缩算法来压缩头部,通讯双方各自缓存一份 header fields 表(下面会介绍),避免重复 header 传输并减小了体积。

# header fields 表

表中用索引代表头部名称或者键值对,前一次通信双方都会记住已发送过哪些首部,后一次发送只需要传输差异的头部,相同的部分直接用索引表示。如:

索引 头部
1 method GET
2 scheme https
... ... ...

# 服务端推送(Server Push)

http2.0 中,服务端直接给客户端发送数据,而无需客户端先进行请求。服务端推送还可以缓存。

但是,与 web socket 双向通信不同。服务器只能主动将资源推送到客户端缓存,并不允许将数据推送到客户端里跑的 Web App 本身。服务端推送对 Web App 是隐藏的,完全由浏览器处理。

# 不断开重连

对于取消请求的操作,

http1.x 是通过设置 tcp segment 里的 reset flag 来通知对端关闭连接的,但会导致连接直接断开,下次请求必须重连。

http2.0 引入 RST_STREAM 类型的帧(frame),可以在不断开连接的前提下取消某个请求的流(stream)。

# SSL 更安全

  • 使用 TLS 的拓展 ALPN 来做协议升级
  • 黑名单机制,禁用几百种不再安全的加密算法
最后更新时间: 7/24/2021, 5:02:34 PM