后浪笔记一零二四

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            |
  |--------------------------------------->|
  |                                        |
  1. 客户端测试工具: curl(7.46.0以上版本)
  1. 客户端发送的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帧
  1. 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中的内容。
  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                    |       |
                             |       |--------------------------------|       |
                             |------------------------------------------------|
  1. 传输中无序,接收时组装
# 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
  1. 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)状态

  1. LENGTH的两个范围的不同含义:
  • 0至2^14 - 1(即16383)
    • 所有实现必须可以支持16KB以下的帧
  • 2^14至2^24 - 1(16,777,215)
    • 传递16KB到16MB的帧时,接收端必须首先公开自己可以处理此大小
      • 通过SETTINGS_MAX_FRAME_SIZE帧(Identifier=5)告知
  1. 帧类型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头部时的持续帧
  2. 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)

  1. 静态字典
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
  1. 动态表
    • 先入后出的淘汰策略
    • 动态表大小由SETTINGS_HEADER_TABLE_SIZE设置帧定义
    • 允许重复项
    • 初始为空
    • 控制新的Header是否进入动态表
      • 进入动态表,供后续传输优化使用
      • 不进入动态表
      • 不进入动态表,并约定该头部永远不进入动态表
<----------- Index Address Space ---------->
<-- Static  Table -->  <-- Dynamic Table -->
+---+-----------+---+  +---+-----------+---+
| 1 |   ...     | s |  |s+1|   ...     |s+k|
+---+-----------+---+  +---+-----------+---+
                       ^                   |
                       |                   v
                   Insertion Point      Dropping Point
  1. 静态表和动态表索引数字的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
  1. Huffman编码
  • 原理:出现概率较大的符号采用较短的编码,概率较小的符号采用较长的编码 比如说,像英文字母中,字母a和e,这样的字母,它的出现的概率可能非常的大,我们就可以采用很小的编码, 因为它可能本身就只有一个字节,我们可以采用更少的位数,比如说5位来表示。 而像有些,特殊的符号,比如罗马数字,这样出现概率较小的符号,我们就采用较长的编码。

  • 静态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帧

  1. 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 (*)         ... |
+--------------------------------------------------------+
  1. 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. 流程
# 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
|  |        }                                        |     |
+--|-------------------------------------------------|-----+
   |                                                 |
  1. PUSH帧的格式
+---------------+
|Pad Length? (8)|
+-+-------------+--------------------------------------+
|R|                  Promised Stream ID (31)           |
+-+---------------------------+------------------------+
|                   Header Block Fragment (*)        ...
+------------------------------------------------------+
|                          Padding (*)               ...
+------------------------------------------------------+

R: 保留位
Promised Stream ID: 告诉客户端我接下来将在哪个Stream中给你发送资源
Header Block Fragment: 告诉资源对应的请求是怎样的
  1. PUSH推送模式的禁用
  • SETTINGS_ENABLE_PUSH(0x2) 1表示启用推送功能 0表示禁用推送功能

3. Stream

3.1 Stream的状态变迁

  1. Stream特性
  • 一条TCP连接上,可以并发存在多个出于OPEN状态的Stream
  • 客户端或者服务器都可以创建新的Stream
  • 客户端或者服务器都可以首先关闭Stream
  • 同一条Stream内的Frame帧是有序的
  • 从Stream ID的值可以轻易分辨PUSH消息
    • 所有为发送HEADER/DATA消息而创建的流,从1、3、5等递增奇数开始
    • 所有为发送PUSH消息而创建的流,从2、4、6等递增偶数开始
  1. Message特性
  • 一条HTTP Message由1个HEADER(可能含有0个或者多个持续帧构成)及0个或者多个DATA帧构成
  • HEADER消息同时包含HTTP/1.1中的start line与headers部分
  • 取消HTTP/1.1中的不定长Chunk消息
  1. 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----------
  1. 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----------
  1. 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支持对不同的请求,做优先级的调整。

  1. 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)
* 数据流间可以有依赖关系
  1. 使用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
  1. 使用HEADER帧来设置
+---------------+
|Pad Length? (8)|
+-+-------------+--------------------------------------------+
|E|                   Stream Dependency?(31)                 |
+-+-------------+--------------------------------------------+
| Weight?(8)    |                                            
+-+-------------+--------------------------------------------+
|                    Header Block Fragment (*)             ...
+------------------------------------------------------------+
|                            Padding(*)                    ...
+------------------------------------------------------------+

4. 不同于TCP的流量控制

不同于TCP协议在传输层提供了流量控制,那么http2呢,在应用层,也提供了流量控制. 而且这个流量控制,既可以针对整个连接,也可以针对每一个Stream流。 当我们发现,网络有性能问题的时候,如果我们理解了http2的流控,就可以更好更快的定位出问题来。

  1. 为什么需要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-->|
  |                                               |
  |                                               |
  1. 由应用层决定发送速度
  • HTTP/2中的流控制既针对单个Stream,也针对整个TCP连接
    • 客户端与服务器都具备流量控制能力
    • 单向流控制:发送和接收独立设定流量控制
    • 以信用为基础:接收端设定上限,发送端应当遵循接收端发出的指令
    • 流量控制窗口(流或者连接)的初始值是65535字节
    • 只有DATA帧服从流量控制
    • 流量控制不能被禁用。
  1. WINDOW_UPDATE
  • type=0x8,不使用任何flag
  • 窗口范围1 to 2^31-1 (2,147,483,647)字节
    • 0是错误的,接收端应返回PROTOCOL_ERROR
  • 当Stream ID为0时表示对连接流控,否则为对Stream流控
  • 流控仅针对直接建立TCP连接的两端
    • 代理服务器并不需要透传WINDOW_UPDATE
      • 接收端的缩小流控窗口会最终传递到源发送端
+-+-----------------------------------------------+
|R|         Window Size Increment (31)            |
+-+-----------------------------------------------+
  1. 流控制窗口
  • 窗口大小由接收端告知
  • 窗口随着DATA帧的发送而减少
    Sender
+-------------------------+
| Flow-Control Window     |
+-------------------------+

+---------------+----+----+
|               |DATA|DATA|
+---------------+----+----+

+---------------+---------+  {DATA} {DATA}
|               |         |----------------->
+---------------+---------+
  1. 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

  1. gRPC:支持多语言编程、基于HTTP/2通讯的中间件
                                     +----------------+
                .---<-Proto Request--|-+------------+ |
+--------------/-+                   |-> gRPC Stub  | |
| +-----------v+ |                   / +------------+ |
| | gRPC Server|-|->Proto Response>-`|  Ruby Client   |
| +-----------\+<--                  +----------------+
|              \ | \
|  c++ Service  \|  \                +-------------------+
+----------------\   -<ProtoRequest--|-+------------+    |
                  ------------------>->| gRPC Stub  |    |
                      ProtoResponse  | +------------+    |
                                     |Android-Java Client|
                                     +-------------------+
  1. Protocol Buffers编码:消息结构
                    Message Struct 
   Field1    |  Field2    |  Field3    |
{Tag}{Value} |{Tag}{Value}|{Tag}{Value}|{} {} {}
  ^          |            |            |
  |
(field_number<<3)|wire_type
  1. 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
  2. 举例

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 」,也可搜索「 后浪笔记一零二四 」找到我。


上一篇 « 下一篇 »

赞赏支持

请我吃鸡腿 =^_^=

i ysf

云闪付

i wechat

微信

推荐阅读

Big Image