# http2
# 二进制分帧
HTTP/2 为什么能在不改动 HTTP/1.x 的语义、方法、状态码以及首部字段等的情况下,改进传输性能,实现低延迟和高吞吐量?
关键之一就是在应用层(http2)和传输层(TCP or UDP)之间增加一个二进制分帧层。
┌───────────────────────────┐
│ HTTP2 │
└─────────────┬─────────────┘ ┌───────────────┐
┌─────────────┴─────────────┐ │ HEADER frame │
│ 二进制分帧层 │─────> │───────────────│
└─────────────┬─────────────┘ │ DATA frame │
┌─────────────┴─────────────┐ └───────────────┘
│ TCP │
└───────────────────────────┘
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) │
└───────────────────────────────┘
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 的性能提升 ———— 只需要创建一个新流即可,这不需要多少时间。单连接有如下优势:
- 单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大
- 由于 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 来做协议升级
- 黑名单机制,禁用几百种不再安全的加密算法