HTTP1.1高延迟的问题:
- 随着带宽的增加,延迟并没有显著下降。
- 并发连接有限,Chrome同一个域名下最多可以打开6个连接。
- 同一个连接只能在完成一个HTTP事务(请求/响应)后才能处理下一个事务。 http2中使用Multiplexing解决了这一问题
- 无状态特性带来的巨大HTTP头部(低网络效率)
- 不支持消息推送
HTTP2 (RFC7520):
- 使用http1.x的客户端和服务器可以无缝地通过代理方式转接到http2上。
- 不识别http2的代理服务器可以将请求降级到http1.x。
浏览器只支持h2,不支持h2c:
- h2: 基于TLS协议运行的HTTP/2被称为h2 如果我们想要在TLS层上直接运行http/2协议, 就需要安装一个ALPN扩展(Application Layer Protocol Negotiation), 这个扩展可以帮助我们从HTTP/1.x升级到HTTP/2。
- h2c: 直接在TCP协议之上运行的HTTP/2被称为h2c
1. 从HTTP1升级到HTTP2
h2和h2c升级协议时的区别:
Client Server Client Server
| | | |
| | | GET /index.html HTTP/1.1 |
| Client Hello: | | Connection:Upgrade,HTTP2-Settings |
|\ ALPN Extension | |\ Upgrade:h2c |
| \ (HTTP/1, h2) | | \ HTTP2-Settings:438997893ab379 |
| --------->---------- | | ------------------->------------ |
| \ | | \ |
| \ | | HTTP/1.1 101 Switching Protocols\ |
| Server Hello \| | Connection: Upgrade \|
| ALPN Extension | | Upgrade: h2c |
| (Selected: h2) | | |
V<------------------------V | <HTTP/2 Frames...> |
V<------------------------------------V
h2使用ALPN h2c类似websocket
建立连接的过程是一样的:
Client Server
| |
|Connection Preface |
|"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" |
|--------------------------------------->|
| |
| SETTINGS Frame |
|--------------------------------------->|
| |
| SETTINGS Frame |
|<---------------------------------------|
| |
| SETTINGS Frame with ACK flag|
|<---------------------------------------|
| |
|SETTINGS Frame with ACK flag |
|--------------------------------------->|
| |
- 客户端测试工具: curl(7.46.0以上版本)
- curl http://nghttp2.org –http2 -v
- http://nghttp2.org不仅支持h2,而且支持h2c
- 客户端发送的Magic帧 16进制转换为ASCII的网站:https://www.rapidtables.com/convert/number/hex-to-ascii.html
- Preface (ASCII编码,12字节)
- 何时发送?
- h2c: 接收到服务器发送来的101 Switching Protocols
- h2: TLS握手成功后
- Preface内容
- 十六进制: 505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
- ASCII值: PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
- 发送完毕后,应紧跟SETTING帧
- 何时发送?
- Application-Layer Protocol Negotiation Extension
- RFC7301
浏览器 Web服务器(nginx)
1.Client Hello ^-------------------------------------->^
(ALPN扩展) | 支持安全套件列表 |
{在该扩展中指定} | |
{支持的协议, } |<--------------------------------------| 2.Server Hello (ALPN扩展)
{如http1.1、h2 } | | {在该扩展中选择h2协议 }
| 选择安全套件 |
check |<--------------------------------------| 3.Server Certificates
certificate | 发送证书 |
validity | |
|<--------------------------------------| 4.ServerKey Exchange
| -ServerDH公钥 |
|<--------------------------------------| 5.Server Hello Done
| |
| |
6.ClientKey Exchange |-------------------------------------->|
| -ClientDH公钥 |
| |
7.生成密钥 | | 7.生成密钥
| |
| |
8.CipherSpec Exchange|-------------------------------------->|
Finished | |
(密钥交换结束) | |
|<--------------------------------------| 8.CipherSpec Exchange(密钥交换结束)
| | Finished
| |
| |
| |
| Encrypted data |
| |
2. 帧、消息、流的关系
Connection---------------------------------------------+
| Stream1-------------------------------------------+ |
| | Request message-+ | |
| | -----------------| {frame} |----> | |
| | +---------------+ | |
| | Response message------+ | |
| | <----|{frame}{frame}{frame}|------------- | |
| | |{frame}{frame}{frame}| | |
| | +---------------------+ | |
| +-------------------------------------------------+ |
| ....多个Stream |
+------------------------------------------------------+
- 连接Connection: 1个TCP连接,包含一个或者多个Stream
- 数据流Stream: 一个双向通讯数据流,包含1条或者多条Message
- 消息Message: 对应HTTP/1中的请求或者响应,包含一条或者多条Frame
- 数据帧Frame: 最小单位,以二进制压缩格式存放HTTP/1中的内容。
- 消息的组成: HEADERS帧与DATA帧
|----------------------| |------------------------------------------------|
|Application(HTTP2.0) | | HTTP1.1 |
| {Binary Framing} |---->| |--------------------------------| |
|----------------------| | | POST /upload HTTP/1.1 |-+ |
|Session(TLS)(optional)| | | Host: www.example.org | |->| |
|----------------------| | | Content-Type: application/json | | | |
| Transport(TCP) | | | Content-Length: 15 |-+ | |
|----------------------| | | | | |
| Network(IP) | | ----| {"msg": "hello"} | | |
|----------------------| | | |--------------------------------| | |
| | | |
| | |--------------------------------| | |
| | | HEADERS frame |<---| |
| | |--------------------------------| |
| |-->| DATA frame | |
| |--------------------------------| |
|------------------------------------------------|
- 传输中无序,接收时组装
# stream内是有序的,stream间是无序的:
HTTP2.0 connection
|-----------------------------------------------|
<---| {stream1} {stream3} {stream3} {stream1} {...} |<---
Client | { DATA} {HEADERS} {HEADERS} { DATA} { } | Server
| |
--->|-------------------------------{stream5}-------|--->
| { DATA} |
|-----------------------------------------------|
2.1 帧格式
+----------------------------------+------------+------------+
| LENGTH(24) | TYPE(8) | FLAGS(8) |
+-+--------------------------------+------------+------------+
|R| STREAM INDENTIFIER(31) |
+-+----------------------------------------------------------+
| FRAME PAYLOADS(0..n) |
+------------------------------------------------------------+
HyperText Transfer Protocol 2
Stream: HEADERS, Stream ID: 1, Length 198, 200 OK
Length: 198
Type: HEADERS(1)
Flags: 0x04
0... .... .... .... .... .... .... .... = Rreserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identified: 1
[Pad Length: 0]
Header Block Fragment: 886196c361be940baa681fa50400bea01ab8005c1
[Header Length: 401]
[Header Count: 13]
Header: :status: 200 OK
Header: date: Fri, 17 May 2019 04:00:23 GMT
Header: content-type: text/html
Header: last-modified: Thu, 18 Apr 2019 06:19:33 GMT
Header: etag: "5cb816f5-19d8"
Header: accept-ranges: bytes
Header: content-length: 6616
Header: x-backend-header-rtt: 0.024989
- Stream ID的作用
-
实现多路复用的关键
- 接收端的实现可据此并发组装消息
- 同一Stream内的frame必须是有序的(无法并发)
SETTINGS_MAX_CONCURRENT_STREAMS
控制着并发Stream数
-
推送依赖性请求的关键
- 由客户端建立的流必须是奇数
- 由服务器建立的流必须是偶数
-
流状态管理的约束性规定
- 新建立的流ID必须大于曾经建立过的状态为opened或者reserved的流ID
- 在新建立的流上发送帧时,意味着将更小ID且为idle状态的流置为closed状态
- Stream ID不能复用,长连接耗尽ID应创建新连接
-
应用层流控,仅影响数据帧
- Stream ID为0的流仅用于传输控制帧
-
在HTTP/1升级到h2c中,以ID为1流返回响应,之后流进入half-closed(local)状态
- LENGTH的两个范围的不同含义:
- 0至2^14 - 1(即16383)
- 所有实现必须可以支持16KB以下的帧
- 2^14至2^24 - 1(16,777,215)
- 传递16KB到16MB的帧时,接收端必须首先公开自己可以处理此大小
- 通过
SETTINGS_MAX_FRAME_SIZE
帧(Identifier=5)告知
- 通过
- 传递16KB到16MB的帧时,接收端必须首先公开自己可以处理此大小
-
帧类型Type
帧类型 类型编码 用途 DATA 0x0 传递HTTP包体 HEADERS 0x1 传递HTTP头部 PRIORITY 0x2 指定Stream流的优先级 RST_STREAM 0x3 终止Stream流 SETTINGS 0x4 修改连接或者Stream流的配置 PUSH_PROMISE 0x5 服务端推送资源时描述请求的帧 PING 0x6 心跳检测,兼具计算RTT往返时间的功能。返回也叫PING,所以没有PONG GOAWAY 0x7 优雅的终止连接或者通知错误 WINDOW_UPDATE 0x8 实现流量控制 CONTINUATION 0x9 传递较大HTTP头部时的持续帧 -
Setting设置帧的子类型
- 设置帧并不是"协商",而是_发送方_向_接收方_通知其特性、能力
- 一个设置帧可同时设置多个对象,放在
FRAME PAYLOADS
中+------------------------+ | Idendifier(16) | +------------------------+-------------------------+ | value(32) | +--------------------------------------------------+
- Identifier: 设置类型
SETTINGS_HEADER_TABLE_SIZE(0x1): 通知对端索引表的最大尺寸(单位字节,初始4096字节) SETTINGS_ENABLE_PUSH(0x2): Value设置为0时可禁用服务器推送能力,1表示启用推送功能 SETTINGS_MAX_CONCURRENT_STREAMS(0x3): 告诉接收端允许的最大并发流数量 SETTINGS_INITIAL_WINDOW_SIZE(0x4): 声明发送端的窗口大小,用于Stream级别流控,初始值2^16-1(65,535)字节 SETTINGS_MAX_FRAME_SIZE(0x5): 设置帧的最大大小,初始值2^14(16,384)字节 SETTINGS_MAX_HEADER_LIST_SIZE(0x6): 知会对端头部索引表的最大尺寸,单位字节,基于未压缩前的头部
- Value: 设置值
2.2 HPACK如何减少HTTP头部的大小?
根据RFC7541规范,有三个组成部分: * 静态字典 * 动态字典 * 压缩算法: Huffman编码(最高压缩比8:5)
- 静态字典
- https://httpwg.org/specs/rfc7541.html#static.table.definition
- header name和value以索引方式编码
- header name以索引方式编码,而header value以字面形式编码
- header name和value都以字面形式编码
- 字面形式编码:要么直接使用ASCII字符,要么使用Huffman编码
index | Header Name | Header Value |
---|---|---|
1 | :authority | |
2 | :method | GET |
3 | :method | POST |
4 | :path | / |
5 | :path | /index.html |
6 | :scheme | http |
7 | :scheme | https |
8 | :status | 200 |
… | … | … |
32 | cookie | |
… | … | … |
60 | via | |
61 | www-authenticate |
举例:
Request headers: Static table
| :method | GET | | 1|:authority| | Encoded headers
| :scheme | https| | 2| :method | GET | | 2|
| :host | example.com | |..| ... | ... | | 7|
| :path | /restore | => |51| referer | | => |63|
|user-agent| Mozilla/5.0..| |..| ... | ... | |19| Huffman("/resource") |
|custom-hdr| some-value | -----------------------------
|62|user-agent|Mozilla/5.0..| |62|
|63|:host |example.com | | Huffman("customer-hdr") |
|..|... | ... | | Huffman("some-value") |
Dynamic table
- 动态表
- 先入后出的淘汰策略
- 动态表大小由
SETTINGS_HEADER_TABLE_SIZE
设置帧定义 - 允许重复项
- 初始为空
- 控制新的Header是否进入动态表
- 进入动态表,供后续传输优化使用
- 不进入动态表
- 不进入动态表,并约定该头部永远不进入动态表
<----------- Index Address Space ---------->
<-- Static Table --> <-- Dynamic Table -->
+---+-----------+---+ +---+-----------+---+
| 1 | ... | s | |s+1| ... |s+k|
+---+-----------+---+ +---+-----------+---+
^ |
| v
Insertion Point Dropping Point
- 静态表和动态表索引数字的N位前缀编码:
# 1. 编码
if l < 2^N - 1
# 第一行
encode l on N bits
else
# 第一行
encode (2^N -1) on N bits
l = l - (2^N -1)
while l >= 128
# 中间的n行
encode (l % 128 + 128) on 8 bits
l = l / 128
# 最后一行
encode l on 8 bits
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| x | x | x | 1 | 1 | 1 | 1 | 1 | 第一行(有N个1)
+---+---+---+-------------------+
| 1 | Value-(2^N-1) LSB | 第二行(首位的1表示还没结尾)
+---+---------------------------+
...
+---+---+---+-------------------+
| 0 | Value-(2^N-1) MSB | 最后一行(首位的0表示已经结尾)
+---+---------------------------+
########################################
########################################
# 2. 解码
decode l from the next N bits
if l < 2^N - 1
return 1
else
M := 0
B := 0
do
B = next octet
# 127=0b1111111,所以B & 127表示去掉首位的1
l = l + (B & 127)*(2^M)
M = M + 7
while B & 128 == 128
return l
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
31| X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 1306
26| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1306>=128, encode(154), I=1306/128
10| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10<128, encode(10), done
10 * 128 + 26 + 31 = 1337
- Huffman编码
-
原理:出现概率较大的符号采用较短的编码,概率较小的符号采用较长的编码 比如说,像英文字母中,字母a和e,这样的字母,它的出现的概率可能非常的大,我们就可以采用很小的编码, 因为它可能本身就只有一个字节,我们可以采用更少的位数,比如说5位来表示。 而像有些,特殊的符号,比如罗马数字,这样出现概率较小的符号,我们就采用较长的编码。
-
静态Huffman编码
- https://httpwg.org/specs/rfc7541.html#huffman.code
- http2使用的是静态Huffman编码
-
动态Huffman编码
- 容易被攻击
-
Huffman数的构造过程
1. 计算各字母的出现概率 2. 将出现概率最小的两个字母构成子树(父节点概率是它们的和),左小右大。 3. 重复步骤2,直至完成树的构造 4. 给树的左连接编码为0,右连接编码为1 5. 每个字母的编码:从根结点至所在叶结点中所有连接的编码。 假设: 有FORGET这六个字母需要编码,其出现频率如下: |Symbol | F | O | R | G | E | T | |Frequency| 2 | 3 | 4 | 4 | 5 | 7 | 1. 将概率为2的F与概率为3的O构成子树,其父结点概率为5 2. 将概率为4的R与概率为4的G构成子树,其父结点概率为8 3. 将概率为5的E与第1步生成的概率为5的子树合并 4. 将概率为7的T与第2步生成的概率为8的子树合并 5. 将第3步生成的概率为10的子树与第4步生成的概率为15的子树合并 25 / \ 0 / \1 / \ / \ 0 10 15 / \ 0 / \ 0 5 \1 8 \ 1 / \ 1 \ 0 / \1 \ 2 3 5 4 4 7 F O E R G T 最终的编码如下 |Symbol | F | O | R | G | E | T |Frequency| 2 | 3 | 4 | 4 | 5 | 7 |CODE |000|001|100|101|01 |11
2.3 HEADER帧
- HEADER帧格式
# 1. HEADER帧的格式
+---------------+
|Pad Length? (8)|
+-+-------------+----------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+----------------------------------------+
| Weight? (8) |
+--------------------------------------------------------+
| Header Block Fragment (*) ... |
+--------------------------------------------------------+
| Padding (*) ... |
+--------------------------------------------------------+
注意,Pad Length, E, Stream Dependency, Weight都是可选的
# 2. CONTINUATION帧的格式
+--------------------------------------------------------+
| Header Block Fragment (*) ... |
+--------------------------------------------------------+
- Header Block Fragment的格式
-
名称与值都在索引表中(包括静态表与动态表)
- 编码方式:首位传1,其余7位传索引号
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | Index (7+) | +---+---------------------------+
- 例如:
- method:GET在静态索引表中序号为2, 其表示应为1000 0010,十六进制表示为82
-
名称在索引表中,值需要编码传递,同时新增至动态表中
- 前2位传01
- 名称举例
- if-none-match在静态索引表中序号为41,表示为01101001,HEX表示为69
- 值举例
- “5cb816f5-19d8”
- value长度15个字节,采用Huffman编码(H为1)
- 10001100
- 8c fe 5b 24 6f 05 c9 5b 58 2f c8 f7 f3
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | Index (6+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+ * H为1表示采用Huffman编码,为0表示不采用Huffman编码
-
名称、值都需要编码传递,同时新增至动态表中
- 前2位传01
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---+-----------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
-
名称在索引表中,值需要编码传递,且不更新至动态表中
- 前4位传0000
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | Index (4+) | +---+---+---+---+---------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
-
名称、值都需要编码传递,且不更新至动态表中
- 前4位传0000
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | +---+---+---+---+---------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
-
名称在索引表中,值需要编码传递,且永远不更新至动态表中
- 前4位传0001
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | Index (4+) | +---+---+---+---+---------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+ 或者 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | Index (4+) | +---+---+---+---+---------------+ | H | Name Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
-
在HEADER帧中直接修改动态表大小
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Max size (5+) |
+---+---------------------------+
2.4 PUSH_PROMISE帧–提前将资源推送至浏览器缓存
推送资源必须对应一个请求,例如给HTML请求推送js文件。
- 流程
# 1. http1.1时,需要两次RTT
浏览器 服务器
| |
|------------------GET /index.html--------------->|
| |
| <html> |
|<-----<LINK REL="stylesheet" ---------|
| type="text/css" href="/some.css"> |
| </html> |
| |
|------------GET /some.css----------------------->|
| |
| h2,h3 { |
|<-------border-bottom: 1px dashed #B9B9B9 |
| } |
# 2. HTTP2时
* 在Stream1中通知客户端CSS资源即将来临
* 在Stream2中发送CSS资源(Stream1和2可以并发!)
浏览器 服务器
| |
+--|-------------------------------------------------|-----+
| |------------------GET /index.html--------------->| |
| | | |
| |<-----PUSH_PROMISE帧:GET /some.css--------------| |
| | | |-- Stream1
| | <html> | |
| |<-----<LINK REL="stylesheet" ---------| |
| | type="text/css" href="/some.css"> | |
| | </html> | |
+--|-------------------------------------------------|-----+
| |
| |
+--|-------------------------------------------------|-----+
| | h2,h3 { | |
| |<-------border-bottom: 1px dashed #B9B9B9 -------| |-- Stream2
| | } | |
+--|-------------------------------------------------|-----+
| |
- PUSH帧的格式
+---------------+
|Pad Length? (8)|
+-+-------------+--------------------------------------+
|R| Promised Stream ID (31) |
+-+---------------------------+------------------------+
| Header Block Fragment (*) ...
+------------------------------------------------------+
| Padding (*) ...
+------------------------------------------------------+
R: 保留位
Promised Stream ID: 告诉客户端我接下来将在哪个Stream中给你发送资源
Header Block Fragment: 告诉资源对应的请求是怎样的
- PUSH推送模式的禁用
SETTINGS_ENABLE_PUSH(0x2)
1表示启用推送功能 0表示禁用推送功能
3. Stream
3.1 Stream的状态变迁
- Stream特性
- 一条TCP连接上,可以并发存在多个出于OPEN状态的Stream
- 客户端或者服务器都可以创建新的Stream
- 客户端或者服务器都可以首先关闭Stream
- 同一条Stream内的Frame帧是有序的
- 从Stream ID的值可以轻易分辨PUSH消息
- 所有为发送HEADER/DATA消息而创建的流,从1、3、5等递增奇数开始
- 所有为发送PUSH消息而创建的流,从2、4、6等递增偶数开始
- Message特性
- 一条HTTP Message由1个HEADER(可能含有0个或者多个持续帧构成)及0个或者多个DATA帧构成
- HEADER消息同时包含HTTP/1.1中的start line与headers部分
- 取消HTTP/1.1中的不定长Chunk消息
- GET消息发送示例
客户端 服务器
| |
|---GET / HTTP/1.1----------------->|
| Host: svrdomain |
| |
| |
| HTTP/1.1 200 OK |
|<--Content-Length: 10 -------------|
| |
| HelloWorld |
| |
Stream ID: 0x1
-------Request---------->
{HEADERS}
Client Server
{HEADERS} {DATA} ... {DATA}
<-------Response----------
- POST消息发送示例
客户端 服务器
| |
|---GET / HTTP/1.1----------------->|
| Host: svrdomain |
| Content-Length: 7 |
| |
| a=1&b=2 |
| |
| |
| HTTP/1.1 200 OK |
|<--Content-Length: 10 -------------|
| |
| HelloWorld |
| |
Stream ID: 0x1
-------Request---------->
{DATA} ... {DATA} {HEADERS}
Client Server
{HEADERS} {DATA} ... {DATA}
<-------Response----------
- Stream流的状态
+-------+
send PP | | recv PP
.--------| idle |--------.
/ | | \
v +-------+ v
+--------+ | +----------+
| | |send H/ | |
.--------|reserved| |recv H | reserved |------.
| |(local) | | | (remote) | |
| +--------+ | +----------+ |
| | +-------+ | |
| | recv ES| |send ES | |
| send H | .------| open |------. |recv H |
| | / | | \ | |
| V v +-------+ v v |
| +--------+ | +----------+ |
| | half | | | half | |
| |closed | |send R/ | closed | |
| |(remote)| |recv R | (local) | |
| +--------+ | +----------+ |
| | | | |
| |send ES/ | recv ES / | |
| |send R / v send R / | |
| |recv R +-------+ recv R | |
| send R / `---------->| |<------------- send R /|
| recv R |closed | recv R |
`----------------------->| |<-----------------------`
+-------+
* /表示或者,例如send H/recv H表示发送或收到HEADER
* 帧符号
* H:HEADERS帧
* PP:`PUSH_PROMISE`帧
* ES:`END_TREAM`标志位
* R:`RST_STREAM`帧
* 流状态
* idle: 起始状态
* closed
* open: 可以发送任何帧
* half closed 单向关闭
* remote: 不再接收数据帧
* local: 不能再发送数据帧
* reserved
* remote
* local
3.2 RST_STREAM帧及常见错误码
HTTP2多个流共享同一个连接,RST帧允许立刻终止一个未完成的流
帧的格式
+---------------------------------------------------------+
| Error Code (32) |
+---------------------------------------------------------+
__常见错误码__
* `NO_ERROR(0x0)`: 没有错误。GOAWAY帧优雅关闭连接时可以使用此错误码
* `PROTOCOL_ERROR(0x1)`:检测到不识别的协议字段
* `INTERNAL_ERROR(0x2)`: 内部错误
* `FLOW_CONTROL_ERROR(0x3)`: 检测到对端没有遵守流控策略
* `SETTINGS_TIMEOUT(0x4)`: 某些设置帧发出后需要接收端应答,在期待时间内没有得到应答则由此错误码表示
* `STREAM_CLOSED(0x5)`: 当Stream已经处于半关闭状态不再接收Frame帧时,又接收到了新的Frame帧
* `FRAME_SIZE_ERROR(0x6)`:接收到的Frame Size不合法
* `REFUSED_STREAM(0x7)`: 拒绝先前的Stream流的执行
* `CANCEL(0x8)`: 表示Stream不再存在
* `COMPRESSION_ERROR(0x9)`:对HPACK压缩算法执行失败
* `CONNECT_ERROR(0xa)`: 连接失败
* `ENHANCE_YOUR_CALM(0xb)`: 检测到对端的行为可能导致负载的持续增加,提醒对方"冷静"一点
* `INADEQUATE_SECURITY(0xc)`: 安全等级不够
* `HTTP_1_1_REQUIRED(0xd)`: 对端只能接受HTTP/1.1协议
3.3 设置Stream的优先级
当我们在浏览器中访问一个页面时,会发起很多个请求,这些请求中,既有访问CSS这样的样式文件, 也会访问javascript文件,也可以去访问jpg/png这样的图片文件, 那么这些请求相对于我们用户的体验来说,它的优先级肯定是不一样的。 比如css文件的优先级就比较高,javascript肯定是次之, jpg等图片的他们的优先级肯定是最低的, http2支持对不同的请求,做优先级的调整。
- Stream优先级
(*) (*) (*) (*)
/ \ | | |
v v v v v
Stream A B D D D
weight 12 4 1 1 1
| | / \
v v v v
C C E C
8 8 8 8
/ \ / \
v v v v
A B A B
12 4 12 4
* 每个数据流有优先级(1-256)
* 数据流间可以有依赖关系
- 使用PRIORITY帧来设置
+-+---------------------------------------------+
|E| Stream Dependency(31) |
+-+---------+-----------------------------------+
| Weight(8) |
+-+---------+
* 帧类型: type=0x2
* 不使用flag标志位字段
* Stream Dependency: 依赖流
* Weight权重:取值范围为1到256。默认权重16
* 仅针对Stream流,若ID为0且想影响连接,则连接端必须报错
* 在idle和closed状态下,仍然可以发送Prioirty帧
E: exclusive标志位,表示独占
D的E设为0 D的E设为1
A
A 新增D A A 新增D |
/ \ ==> / | \ / \ ==> D
B C B D C B C / \
B C
- 使用HEADER帧来设置
+---------------+
|Pad Length? (8)|
+-+-------------+--------------------------------------------+
|E| Stream Dependency?(31) |
+-+-------------+--------------------------------------------+
| Weight?(8) |
+-+-------------+--------------------------------------------+
| Header Block Fragment (*) ...
+------------------------------------------------------------+
| Padding(*) ...
+------------------------------------------------------------+
4. 不同于TCP的流量控制
不同于TCP协议在传输层提供了流量控制,那么http2呢,在应用层,也提供了流量控制. 而且这个流量控制,既可以针对整个连接,也可以针对每一个Stream流。 当我们发现,网络有性能问题的时候,如果我们理解了http2的流控,就可以更好更快的定位出问题来。
- 为什么需要HTTP/2应用层流控
- HTTP/1.1中由TCP层进行流量控制
- 前提: HTTP/1的TCP连接上没有多路复用
(a)串行连接
----事务1--- ----事务2--- -----事务3--- ----事务4-----
------^------------^-------------^---------------^----------------->
* TCP接收窗口 -----> 服务器 / \ / \ / \ / \
* TCP拥塞控制 / \ / \ / \ / \
客户端 / \ / \ / \ / \
-|--------v|-----------v|------------v|-------------v-------------->
连接1 连接2 连接3 连接4 时间
(b)持久连接
----事务1-- -事务2-- -事务3-- -事务4--
------^--------^--------^--------^----------------->
* TCP接收窗口 -----> 服务器 / \ / \ / \ / \
/ \ / \ / \ / \
客户端 / \ / \ / \ / \
-|--------v|-------v|-------v|-------v------------->
连接1 连接2 连接3 连接4 时间
- HTTP/2中,多路复用意味着多个Stream必须共享TCP层的流量控制
- 问题:多Stream争夺TCP的流控制,互相干扰可能造成Stream阻塞
客户端 服务器
| |
| |
|--F1+ES--F2+ES--F4+ES--F3+ES--F4--F3--F2--F1-->|
| |
| |
- 由应用层决定发送速度
- HTTP/2中的流控制既针对单个Stream,也针对整个TCP连接
- 客户端与服务器都具备流量控制能力
- 单向流控制:发送和接收独立设定流量控制
- 以信用为基础:接收端设定上限,发送端应当遵循接收端发出的指令
- 流量控制窗口(流或者连接)的初始值是65535字节
- 只有DATA帧服从流量控制
- 流量控制不能被禁用。
WINDOW_UPDATE
帧
- type=0x8,不使用任何flag
- 窗口范围1 to 2^31-1 (2,147,483,647)字节
- 0是错误的,接收端应返回
PROTOCOL_ERROR
- 0是错误的,接收端应返回
- 当Stream ID为0时表示对连接流控,否则为对Stream流控
- 流控仅针对直接建立TCP连接的两端
- 代理服务器并不需要透传
WINDOW_UPDATE
帧- 接收端的缩小流控窗口会最终传递到源发送端
- 代理服务器并不需要透传
+-+-----------------------------------------------+
|R| Window Size Increment (31) |
+-+-----------------------------------------------+
- 流控制窗口
- 窗口大小由接收端告知
- 窗口随着DATA帧的发送而减少
Sender
+-------------------------+
| Flow-Control Window |
+-------------------------+
+---------------+----+----+
| |DATA|DATA|
+---------------+----+----+
+---------------+---------+ {DATA} {DATA}
| | |----------------->
+---------------+---------+
SETTINGS_MAX_CONCURRENT_STREAMS
并发流
- 并发仅统计open或者half-close状态的流(不包含用于推送的reserved状态)
- 超出限制后的错误码
PROTOCOL_ERROR
REFUSED_STREAM
5. HTTP/2与gRPC框架
HTTP2不止可以用在浏览器访问服务器上,我们在服务器之间通讯时,也可以使用http2协议。 比如我们经常使用的gRPC框架,它就是建立在HTTP2协议之上的。这节课我们将以gRPC框架为例, 给大家演示一下,使用gRPC发送rpc消息的时候,这些消息在http2上,究竟是怎样编码的。
注意,wireshark中,50051端口默认不会被识别为http2,所以需要手动设置“解码为HTTP/2” * 选中某个报文,右键->解码为->将TCP port:50051的当前值改为HTTP2
- gRPC:支持多语言编程、基于HTTP/2通讯的中间件
+----------------+
.---<-Proto Request--|-+------------+ |
+--------------/-+ |-> gRPC Stub | |
| +-----------v+ | / +------------+ |
| | gRPC Server|-|->Proto Response>-`| Ruby Client |
| +-----------\+<-- +----------------+
| \ | \
| c++ Service \| \ +-------------------+
+----------------\ -<ProtoRequest--|-+------------+ |
------------------>->| gRPC Stub | |
ProtoResponse | +------------+ |
|Android-Java Client|
+-------------------+
- Protocol Buffers编码:消息结构
Message Struct
Field1 | Field2 | Field3 |
{Tag}{Value} |{Tag}{Value}|{Tag}{Value}|{} {} {}
^ | | |
|
(field_number<<3)|wire_type
-
Wire Type
Type Meaning Used For 0 Varint int32,int64,uint32,uint64,sint32,sint64,bool,enum 1 64-bit fixed64,sfixed64,double 2 Length-delimited string, bytes, embeded messages,packed repeated fields 3 Start group groups(deprecated) 4 End group groups(deprecated) 5 32-bit fixed32,sfixed32,float -
举例
Protocol Buffers
+------------------------------------------------------------+
field tag=1 type2(string) | length6 M a r t i n |
+-+-+-+-+-+-+-+-+ | +--+ +--+ +--+--+--+--+--+--+ |
|0 0 0 0 1|0 1 0|---------->|0a| |06| |4d 61 72 74 69 6e| |
+-+-+-+-+-+-+-+-+ 0a | +--+ +--+ +--+--+--+--+--+--+ |
| 1和0都表示序号,可以发现序号是逆序的|
field tag=2 type0(varint) | / / |
+-+-+-+-+-+-+-+-+ | +--+ +-----+ +-+-------------+-+-+-------------+ |
|0 0 0 1 0|0 0 0|---------->|10| |b9 0a|<-------|1|0 1 1 1 0 0 1| |0|0 0 0 1 0 1 0| |
+-+-+-+-+-+-+-+-+ 10 | +--+ +-----+ +-+-------------+-+-+-------------+ |
| |
field tag=3 type2(string) | length11 d a y d r e a m i n g |
+-+-+-+-+-+-+-+-+ | +--+ +--+ +--+--+--+--+--+--+--+--+--+--+--+ |
|0 0 0 1 1|0 1 0|---------->|1a| |0b| |64 61 79 64 72 65 61 6d 69 6e 67| |
+-+-+-+-+-+-+-+-+ 1a | +--+ +--+ +--+--+--+--+--+--+--+--+--+--+--+ |
+------------------------------------------------------------+
本文发表于 0001-01-01,最后修改于 0001-01-01。
本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。