Http2协议简介

1.概述

  • 和http1兼容。HTTP/2 没有改动 HTTP 的应用语义。 HTTP 方法、状态代码、URI 和标头字段等核心概念一如往常。 不过,HTTP/2 修改了数据格式化(分帧)以及在客户端与服务器间传输的方式。因此,所有现有的应用都可以不必修改而在新协议下运行。
  • 传输方式改变。在HTTP/1.x中,每个 TCP 连接同时只能处理一个请求 - 响应,一个请求就会独占一个链接,用户想要多个并行的请求来提高性能,必须得使用多个TCP连接(对于同一个域名Chrome最多只能同时创建 6 个 TCP 连接)。这也是被人吐槽的原因,也是http2 要解决的一个痛点,解决方法是在链接的基础上提出了stream的概念,通过stream 来区别不同的请求 。

2.连接多路复用

在一个 TCP 连接上,我们可以向对方不断发送帧,每帧的 stream identifier 的标明这一帧属于哪个流,然后在对方接收时,根据 stream identifier 拼接每个流的所有帧组成一整块数据。

把 HTTP/1.1 每个请求都当作一个流,那么多个请求变成多个流,请求响应数据分成多个帧,不同流中的帧交错地发送给对方,这就是 HTTP/2 中的多路复用。

流的概念实现了单连接上多请求 - 响应并行,解决了线头阻塞的问题。

(1) 帧

+---+-----------+------------+-------------+-------------+
|Bit|   0...7   |   8...15   |   16...23   |   24...31   |
+---+-----------+------------+-------------+-------------+
|0  |              Length                  |     Type    |
+---+-----------+--------------------------+-------------+
|32 |Flags      |
+---+-+---------+----------------------------------------+
|40 |R|                Stream Identifier                 |
+---+-+--------------------------------------------------+
|...|                   Frame Payload                    |
+---+----------------------------------------------------+

字段含义:

  • Length 代表整个 frame 的长度,用一个 24 位无符号整数表示。
  • Type 定义 frame 的类型,用 8 bits 表示。帧类型决定了帧主体的格式和语义,如果 type 为 unknown 应该忽略或抛弃。
  • Flags 是为帧类型相关而预留的布尔标识。标识对于不同的帧类型赋予了不同的语义。如果该标识对于某种帧类型没有定义语义,则它必须被忽略且发送的时候应该赋值为 (0x0) 。
  • R 是一个保留的比特位。这个比特的语义没有定义,发送时它必须被设置为 (0x0), 接收时需要忽略。
  • Stream Identifier 用作流控制,用 31 位无符号整数表示。客户端建立的 sid 必须为奇数,服务端建立的 sid 必须为偶数,值 (0x0) 保留给与整个连接相关联的帧 (连接控制消息),而不是单个流 。
  • Frame Payload 是主体内容,由帧类型决定。

共分为十种类型的帧:

  • HEADERS: 报头帧 (type=0x1),用来打开一个流或者携带一个首部块片段
  • DATA: 数据帧 (type=0x0),装填主体信息,可以用一个或多个 DATA 帧来返回一个请求的响应主体
  • PRIORITY: 优先级帧 (type=0x2),指定发送者建议的流优先级,可以在任何流状态下发送 PRIORITY 帧,包括空闲 (idle) 和关闭 (closed) 的流
  • RST_STREAM: 流终止帧 (type=0x3),用来请求取消一个流,或者表示发生了一个错误,payload 带有一个 32 位无符号整数的错误码 (Error Codes),不能在处于空闲 (idle) 状态的流上发送 RST_STREAM 帧
  • SETTINGS: 设置帧 (type=0x4),设置此 连接 的参数,作用于整个连接
  • PUSH_PROMISE: 推送帧 (type=0x5),服务端推送,客户端可以返回一个 RST_STREAM 帧来选择拒绝推送的流
  • PING: PING 帧 (type=0x6),判断一个空闲的连接是否仍然可用,也可以测量最小往返时间 (RTT)
  • GOAWAY: GOWAY 帧 (type=0x7),用于发起关闭连接的请求,或者警示严重错误。GOAWAY 会停止接收新流,并且关闭连接前会处理完先前建立的流
  • WINDOW_UPDATE: 窗口更新帧 (type=0x8),用于执行流量控制功能,可以作用在单独某个流上 (指定具体 Stream Identifier) 也可以作用整个连接 (Stream Identifier 为 0x0),只有 DATA 帧受流量控制影响。初始化流量窗口后,发送多少负载,流量窗口就减少多少,如果流量窗口不足就无法发送,WINDOW_UPDATE 帧可以增加流量窗口大小
  • CONTINUATION: 延续帧 (type=0x9),用于继续传送首部块片段序列,见 首部的压缩与解压缩

(2) 消息

一个HTTP2请求或响应被分为N个帧传送,在另一端再重新组合为完整的消息。

(3) 流

流是一个逻辑上的概念,代表 HTTP/2 连接中在客户端和服务器之间交换的独立双向帧序列,每个帧的 Stream Identifier 字段指明了它属于哪个流。 流有以下特性:

  • 单个连接可以包含多个流,两端之间可以交叉发送不同流的帧
  • 流可以由客户端或服务器来单方面地建立和使用,或者共享
  • 流可以由任一方关闭
  • 帧在流上发送的顺序很重要,最后接收方会把相同 Stream Identifier (同一个流) 的帧重新组装成完整的消息

3.HTTP头压缩

简单说,HTTP头压缩需要在HTTP/2 客户端和服务端之间:

  • 维护一份相同的静态表(Static Table),包含常见的头部名称,以及特别常见的头部名称与值的组合;
  • 维护一份相同的动态表(Dynamic Table),可以动态地添加内容;
  • 基于静态哈夫曼码表的哈夫曼编码(Huffman Coding);

(1) 静态索引

静态索引表是固定的,对于客户端服务端都一样,目前协议商定的静态索引包含 61 个键值,详见 Static Table Definition - RFC 7541

前几个如下

索引字段值键值
1 :authority  
2 :method GET
3 :method POST
4 :path /
5 :path /index.html
6 :scheme http
7 :scheme https
8 :status 200

(2) 动态索引

动态索引表是一个 FIFO 队列维护的有空间限制的表,里面含有非静态表的索引。
动态索引表是需要连接双方维护的,其内容基于连接上下文,一个 HTTP2 连接有且仅有一份动态表。
当一个首部匹配不到索引时,可以选择把它插入动态索引表中,下次同名的值就可能会在表中查到索引并替换。
但是并非所有首部键值都会存入动态索引,因为动态索引表是有空间限制的,最大值由 SETTING 帧中的 SETTINGS_HEADER_TABLE_SIZE (默认 4096 字节) 设置

4.HTTP/2 的协议协商机制

客户端在不知道服务器是否支持HTTP2协议时需要使用HTTP Upgrade 机制。

客户端首先发起一个 HTTP/1.1 请求,其中包含Upgrade首部字段,还必须包含一个且只能一个 HTTP2-Settings 首部字段。

例如:

GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
  • Upgrade。"h2c" 标示运行在明文 TCP 之上的 HTTP/2 协议。"h2" 标示使用了 TLS(Transport Layer Security)[TLS12] 的 HTTP/2 协议
  • HTTP2-Settings。是一个连接相关的首部字段,它提供了用于管理 HTTP/2 连接的参数(前提是服务端接受了升级请求)

不支持 HTTP/2 的服务端响应请求时,可以认为 Upgrade 首部字段不存在。

支持 HTTP/2 的服务器响应状态码 101 (Switching Protocols) 表示接受升级协议的请求。在结束 101 响应之后,服务端可以开始发送 HTTP/2 帧。

例如:

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

[ HTTP/2 connection ...

客户端一收到 101(Switching Protocols) 响应(表示成功升级)后,就发送客户端连接前奏。客户端连接前奏以一个固定的24字节的序列开始,用十六进制表示为:0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a(相当于一个魔法值,在WireSark可以看到标识为Magic)。

为了避免不必要的延迟,允许客户端发送完连接前奏后就立即向服务端发送其他的帧,而不必等待服务端的连接前奏。

服务端发送的第一个 HTTP/2 帧必须是由一个 SETTINGS 帧组成的服务端连接前奏。

两端都要发送一个连接前奏,作为对所使用协议的最终确认,并确定 HTTP/2 连接的初始设置。

5.流量控制

多路复用的流会竞争 TCP 资源,进而导致流被阻塞。流控制机制确保同一连接上的流不会相互干扰。流量控制作用于单个流或整个连接。HTTP/2 通过使用 WINDOW_UPDATE 帧来提供流量控制。 流控制具有以下特征:

  • 流量控制是特定于连接的。两种级别的流量控制都位于单跳的端点之间,而不是整个端到端的路径。比如 server 前面有一个 front-end proxy 如 Nginx,这时就会有两个 connection,browser-Nginx, Nginx—server,flow control 分别作用于两个 connection。详情见: How is HTTP/2 hop-by-hop flow control accomplished? - stackoverflow
  • 流量控制是基于 WINDOW_UPDATE 帧的。接收方公布自己打算在每个流以及整个连接上分别接收多少字节。这是一个以信用为基础的方案。
  • 流量控制是有方向的,由接收者全面控制。接收方可以为每个流和整个连接设置任意的窗口大小。发送方必须尊重接收方设置的流量控制限制。客户方、服务端和中间代理作为接收方时都独立地公布各自的流量控制窗口,作为发送方时都遵守对端的流量控制设置。
  • 无论是新流还是整个连接,流量控制窗口的初始值是 65535 字节。
  • 帧的类型决定了流量控制是否适用于帧。目前,只有 DATA 帧会受流量控制影响,所有其它类型的帧并不消耗流量控制窗口的空间。这保证了重要的控制帧不会被流量控制阻塞。
  • 流量控制不能被禁用。
  • HTTP/2 只定义了 WINDOW_UPDATE 帧的格式和语义,并没有规定接收方如何决定何时发送帧、发送什么样的值,也没有规定发送方如何选择发送包。具体实现可以选择任何满足需求的算法。

(1) WINDOW_UPDATE 帧格式

+-+-------------------------------------------------------------+
|R|                Window Size Increment (31)                   |
+-+-------------------------------------------------------------+
  • Window Size Increment 表示除了现有的流量控制窗口之外,发送端还可以传送的字节数。取值范围是 1 到 2^31 - 1 字节。

WINDOW_UPDATE 帧可以是针对一个流或者是针对整个连接的。如果是前者,WINDOW_UPDATE 帧的流标识符指明了受影响的流;如果是后者,流标识符为 0 表示作用于整个连接。
流量控制功能只适用于被标识的、受流量控制影响的帧。文档定义的帧类型中,只有 DATA 帧受流量控制影响。除非接收端不能再分配资源去处理这些帧,否则不受流量控制影响的帧必须被接收并处理。如果接收端不能再接收帧了,可以响应一个 FLOW_CONTROL_ERROR 类型的流错误或者连接错误。

(2) 流量控制窗口

流量控制窗口是一个简单的整数值,指出了准许发送端传送的数据的字节数。窗口值衡量了接收端的缓存能力。

新建连接时,流和连接的初始窗口大小都是 2^16 - 1(65535) 字节。可以通过设置连接前言中 SETTINGS 帧的 SETTINGS_INITIAL_WINDOW_SIZE 参数改变流的初始窗口大小,这会作用于所有流。而连接的初始窗口大小不能改,但可以用 WINDOW_UPDATE 帧来改变流量控制窗口,这是为什么连接前言往往带有一个WINDOW_UPDATE 帧的原因。

 

posted @ 2019-12-18 15:23  忧伤的小毛驴  阅读(2669)  评论(0编辑  收藏  举报