QUIC协议和HTTP3.0技术研究
阅读原文时间:2023年07月10日阅读:1

QUIC:基于UDP的安全可靠的HTTP/2传输协议

QUIC(Quick UDP Internet Connection)是一个新的基于UDP的管线化技术和安全传输协议。

QUIC提供:

  • 和HTTP/2相同的管线化技术和流量控制功能
  • 和TLS一样的安全性
  • 而且提供面向连接的可靠传输功能(UDP无连接)
  • 和TCP相同的拥塞控制

QUIC完全运行在用户空间。

因为运行在用户空间,QUIC能够更快的创新,而现存的TCP的更新周期要明显更长。

这篇文档只是描述了QUIC的概念设计和wire specification。

QUIC-CRYPTO:提供了关于加密和握手有关的介绍。

draft-quic-loss-recovery:提供了有关超时重传和拥塞控制有关的介绍。

所有的QUIC中使用的数值,包括长度length,版本version,类型type等都是小段字节序而不是网络字节序(大端)。

下面是一些文档中使用的各种定义:

  • Clinet: 初始化QUIC连接的终端
  • Server:接收QUIC连接请求的终端
  • 终端(Endpoint):指Client或者Server
  • 流Stream:逻辑链路上的全双工字节流
  • 连接Connection:是指连个终端之间的一种约定(conversation),包含了多个流(multiplexes streams)使用的加密环境(encryption context)
  • 连接标识(Connection ID):用来标识一个QUIC连接
  • QUIC 包(packet):一个可以被QUIC接收器分析处理的UDP数据部分,QUIC packet 大小就是指UDP数据部分的长度。

下面简单列举了一些QUIC的关键技术和优点。QUIC在功能上等价于TCP+TLS+HTTP/2。QUIC优于TCP+TLS+HTTP/2的点有:

  • 连接建立延迟
  • 灵活的拥塞控制
  • 不存在head-of-line blocking的管线化技术
  • Authenticated and encrypted header and payload
  • 流和链接的两层流量控制
  • Forward error correction
  • 链接迁移(Connection migration)

3.1、链接建立

简单来说,client第一次和server简历连接的时候,会发送一个空的client hello(CHLO),然后server回复一个rejection(REJ),并且附带上一些建立连接需要的信息(和建立安全连接有关)。当下一次client发送CHLO的时候,client可以利用上次收到的信息(安全证书credentials),直接发送加密(encrypted)之后的请求。

所以说大多数时候,QUIC协议的client只需要zero roundtrips(也就是不需要提前建立连接)就可以直接发送数据。

详细的内容参考QUIC Crypto design

3.2、灵活的拥塞控制

QUIC包含一个插件式的拥塞控制方式和richer signaling than TCP,这样使QUIC能够提供更丰富的拥塞控制信息。现在,QUIC的拥塞控制协议是TCP Cubic的实现,正在尝试使用另外的方式。

One example of richer information is that each packet, both original and retransmitted, carries a new packet sequence number. This allows a QUIC sender to distinguish ACKs for retransmissions from ACKs for original transmissions, thus avoiding TCP's retransmission ambiguity problem. QUIC ACKs also explicitly carry the delay between the receipt of a packet and its acknowledgment being sent, and together with the monotonically-increasing packet numbers, this allows for precise roundtrip-time (RTT) calculation.

QUIC的ACK帧允许最多256个NACK范围,所以在乱序到达的情况下,QUIC的表现要优于TCP连接。

QUIC提供流和链接两个层面的流量控制,类似于HTTP/2。

QUIC的流(stream)层面的流量控制和TCP的滑动窗口流量控制方式相似。

QUIC的链接层面的流量控制和流层面的也相似,只是滑动窗口的大小是所有流的总和。

HTTP中的,允许在第一个请求没有完成前直接发送第二个请求,以便能够使用同一个链接同时发送多个请求的数据,这叫管线化技术。

但是这会引入一个问题:如果第一个请求的消息发生了数据丢失,TCP中为了保证消息的按顺序到达,就会发生阻塞直到重传丢失的数据。在这个过程中,哪怕第二个请求与第一个请求并没有关系而且数据没有丢失,也会导致第二个请求被迫发生阻塞。这就叫head-of-line blocking(线头阻塞)

在QUIC中,一个流的数据发生丢失最多只会影响当前流,对于整个链接的影响不大,每一个流的帧到达都会立马被相应的流处理。

Caveat: QUIC currently compresses HTTP headers via HTTP/2 HPACK header compression, which imposes head-of-line blocking for header frames only.

为了不用等到重传就可以恢复丢失的数据包,QUIC现在使用一种简单的XOR-based(异或) FEC模式。

注:具体的原理还不是很了解,但是大致的效果就是能够通过之后的数据包计算出丢失的数据包,类似于可以纠错的汉明码

QUIC使用由client随机生成的64位connection ID来标识一个连接。

传统的TCP连接由server ip、server port、client ip、client port四项来表示,当client的IP地址发生变化(从WiFi切换到4G网络),之前建立的连接将失效,必须建立新的连接。

而QUIC协议在client的IP地址发生变化之后,依旧可以使用之前使用的connection ID来继续传输数据。

QUIC有Special Packets 和 Regular Packets两种数据包

特殊包:

1. 版本协商包 Version Negotiation Packet

2. Public Reset Packets

普通包:

1. 帧数据包 Frame Packet

2. FEC 包(在3.6中提到过,用来实现前向纠错)

QUIC每种包都应该被分割为适应传输路径上的MTU的大小,避免IP fragmentation。Path MTU 发现算法现在还在研究中。

现在的QUIC实现中,IPv6使用1350的maximum QUIC packet size,IPv4使用1370;

4.1 Public Packet Header

所有的QUIC数据包前面都包含一个2~19字节的public header,格式如下:

        0        1        2        3        4            8
   +--------+--------+--------+--------+--------+---    ---+
   | Public |    Connection ID (0, 8, 32, or 64)    ...    | ->
   |Flags(8)|      (variable length)                       |
   +--------+--------+--------+--------+--------+---    ---+

        9       10       11        12
   +--------+--------+--------+--------+
   |      QUIC Version (32)            | ->
   |         (optional)                |
   +--------+--------+--------+--------+

       13      14       15        16        17       18
   +--------+--------+--------+--------+--------+--------+
   |         Packet Number (8, 16, 32, or 48)            |
   |                         (variable length)           |
   +--------+--------+--------+--------+--------+--------+
  • Public Flags:

    • 0x01 = PUBLIC_FLAG_VERSION,这个符号的含义取决于发送者是client还是server。放client发送的数据包中这一位为1的意思是这个数据包中包含QUIC Version,在收到服务器的同意请求之前,client发送的所有数据包中,这一位必须都设置为1。如果server同意了当前版本号,则会在回包中将这一位清0,如果不同意,则这一位置1,表示这是一个Version协商包,版本协商包的具体细节在之后会讨论。
    • 0x02 = PUBLIC_FLAG_RESET,用来表示当前包是一个Public Reset Packet。
    • 0x0C,也就是flags中的第三第四比特,他用来表示后面的Connection ID的长度,对应的值如下
      • 0x0C 8字节Connection ID
      • 0x08 4字节Connection ID
      • 0x04 1字节Connection ID
      • 0x00 忽略Connection ID
    • 0x30,也就是flags中的第五和第六比特,用来表示Packet Number的字节数,这两位只在Frame Packets之中使用,在Public Reset和Version协商包中,没有使用到Packet Number,所以这两位必须被设置为0;
      • 0x30 6字节packet number
      • 0x20 4字节packet number
      • 0x10 2字节packet number
      • 0x00 1字节packet number
    • 0x40,保留给multipath使用
    • 0x80,未使用,设置为0
  • Connection ID

    一个由client静态随机生成的8字节随机数,用来标识一个connection,这就是前面提到用QUIC用来实现Connection Migration的方式,如果可以的话,可以使用8字节的一部分来标识这个链接,这由public Flags中的第三第四位来设置。

  • QUIC Verison

    大小为32比特,用来表示QUIC协议的版本。只有在Public Flags中FLAG_VERSION位置位的时候才会出现在首部中(public_flags & FLAG_VERSION != 0)。

  • Packet Number

    client和server交换的第一个Regular Packet的序列号应该为1,之后发送的每一个包的序列号都自增一。

Public Flags的处理流程如下图所示:

Check the public flags in public header
                 |
                 |
                 V
           +--------------+
           | Public Reset |    YES
           | flag set?    |---------------> Public Reset Packet
           +--------------+
                 |
                 | NO
                 V
           +------------+          +-------------+
           | Version    |   YES    | Packet sent |  YES
           | flag set?  |--------->| by server?  |--------> Version Negotiation
           +------------+          +-------------+               Packet
                 |                        |
                 | NO                     | NO
                 V                        V
           Regular Packet         Regular Packet with
                              QUIC Version present in header

4.2、Special Packet

4.2.1、版本协商包

只有服务器会发送版本协商包,版本协商包的格式如下图所示,其中Public Flags中的PUBLIC_FLAG_VERSION位必须置1。

     0        1        2        3        4        5        6        7       8
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| Public |    Connection ID (64)                                                 | ->
|Flags(8)|                                                                       |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+

     9       10       11        12       13      14       15       16       17
+--------+--------+--------+--------+--------+--------+--------+--------+---...--+
|      1st QUIC version supported   |     2nd QUIC version supported    |   ...
|      by server (32)               |     by server (32)                |
+--------+--------+--------+--------+--------+--------+--------+--------+---...--+

4.2.1、Public Reset Packet

TODO

4.3、Regular Packet

这里涉及到对数据的两种操作:认证加密。还有另一个概念是数字签名,关于这几个概念的定义可以看这篇文章数字证书原理

TODO Private Header的格式

4.3.1、Frame Packet

除了Private Header,Frame Packet的负载是下图所示的格式,Type的具体格式在后面定义

 +--------+---...---+--------+---...---+
 | Type   | Payload | Type   | Payload |
 +--------+---...---+--------+---...---+

4.3.2、FEC Packet

TODO

5.1、连接的建立

QUIC连接的建立由client发起。

  1. 版本协商:

    client发送给server的最初几个数据包中,必须设置verison flag标记,然后表明自己使用的版本号。client发送的所有数据包都必须设置version flag,直到client收到来自于server的置零version flag的数据包(server通过置零version flag表示同意client的版本请求)。当server收到来自于client的version flag置零的数据包后,任何之后的version flag置1的数据包将被丢弃(delay)。

    对于server收到一个链接建立请求的包之后(新的connection ID,置1 version flag),server会比较包中的verison和它支持的所有版本号,如果这个版本是支持的,那么整个链接的生命周期里都将使用这个版本来发送数据。之后,server发送的所有数据都会清零version flag

    如果这个server不支持当前的版本号,那么会引入一个额外的RTT来进行协商,server会发送一个版本协商包给client。

    如果客户端收到一个版本协商包,那么会从中选择一个可以接受的版本号,然后从新发送一个连接建立请求。最后,client会收到第一个来自server的Regular Packet。

    加密握手通过一个特定的加密流(crypto stream)来进行,Stream ID 为 1

5.2、数据传输

QUIC提供可靠性连接、拥塞控制和流量控制。QUIC的流量控制方式类似于HTTP/2的流量控制。可靠连接和拥塞控制在其他文件中描述。

A QUIC connection uses a single packet sequence number space for shared congestion control and loss recovery across the connection.

所有的数据除了ACK 确认包,都是使用链接内的流来传输的。

这个部分描述了使用流在QUIC链接内传输的理论上的描述。涉及到的各种帧格式在Frame Types and Formats章节中描述。

5.2.1、QUIC流的生命周期

Stream是被切割成stream frame的双向数据流。client和server都可以创建和销毁流,并且每个流之间的数据传输互相没有影响。QUIC流的生命周期和HTTP/2中的流[RFC7540]非常相似。

Stream流可以通过发送对应流的STREAM帧来隐式的创建。为了避免ID碰撞,由server创建的Stream-ID为偶数,由client创建的Stream-ID则为奇数。Stream-ID从1开始记数,并且客户端创建的第一个流——Stream 1专用于crypto handshake(加密握手)。当使用基于HTTP/2的QUIC协议时,Stream 3专用于传输其他流的压缩头部,用来保证头部的按序到达和处理。

Stream-ID在client或者server端都必须单调的增加。例如Stream 2可以在Stream 3之后创建,但是Stream 7一定不可以在Stream 9之后创建。注意,client或者server或许会收到乱序的stream。例如client或许会在收到任何Stream 7的消息之前收到了Stream 9的消息,这可能是因为乱序到达的原因导致的,client需要有一定的机制,支持乱序stream-ID(TODO:chromium中是如何解决这个问题的)。

如果终端收到了一个它不愿意接收的STREAM帧,它可以立刻回复一个RST_STREAM帧(之后会介绍)。注意,即使发起这个stream的另一端在这个帧中已经发送了一些数据,这些数据也必须被丢掉。

一旦流被成功创建,它就可以被用来发送和接收数据。这意味着直到流被关闭,否则终端可以一直发送stream帧数据。

QUIC流的任何一端都可以正常的关闭流。流的关闭有三种方式:

  • 正常关闭

    因为流是双向的,流可以被“半关闭”或者“关闭“。当一端发送带有FIN标志为true的帧之后,这个流被认为在那个方向半关闭了,FIN意味着发送者之后不会再发送数据。当QUIC流的两端都发送和接收了带有FIN标志的帧之后,意味着这个流关闭了。

  • 异常关闭

    client或者server在任何时候都可以发送RST_STREAM帧。RST_STREAM帧中携带了错误代码来表示发生错误的原因(文章后面会给出错误代码的列表),当stream的发起者发送了RST_STREAM帧,这表示发生了无法正确完整数据传输的错误,并且不会再有其他数据。当stream的接收者发送了RST_STREAM帧,stream发起者收到之后就应该停止发送数据。

  • 如在下一节中将描述的,链接关闭之后其中的流也会关闭。

5.3、 链接关闭

当链接空闲超过一个之前约定好的时限之后,就可以关闭链接。当server关闭链接的时候,应该避免告知client,这样可以避免唤醒移动设备上的无线设备。有两种方式能够关闭链接:

  1. 显式关闭:

    终端可以发送CONNECTION_CLOSE 帧给另一端来表示关闭链接。另外,终端也可以在发送CONNECTION_CLOSE 帧之前,发送GOAWAY帧给另一端来表示链接将要被关闭。GOAWAY帧意味着正在活动的流将会继续处理,但是GOAWAY帧的发送端不会再发起或者接收任何新的流。如果一端在还有活动流(没有FIN为true或者RST_STREAM帧发送)的时候,发送了CONNECTION_CLOSE 帧,那么另一端将会认为这些流传输的数据不完整,并且异常中断了。

  2. 隐式关闭:

    默认的QUIC链接空闲时间为30秒,而且空闲时间是在建立链接时商量的一个参数(“ICSL”)。最大时间是10分钟。当没有任何数据传输的时间超过了空闲时间限制,这个链接将会被关闭。默认情况下,将会发送CONNECTION_CLOSE帧。当显式关闭代价较高时(例如对于移动设备,这将会唤醒移动设备的无线电),可以选择激活静默式关闭。

终端可以在任何时候发送PUBLIC_RESET 帧来表示活动链接的异常终止。在QUIC中PUBLIC_RESET 等价于TCP中的RST。

QUIC帧包由帧填充(应该理解为帧包就是由包头部加上帧组成的)。它有一个帧类型字节,后跟类型相关的帧头字段。所有帧都包含在单个QUIC包中,没有帧可以跨越QUIC包边界(不存在TCP中的IP数据分包)。

6.1、帧类型

这里有两种帧类型:特殊类型(Special Frame Type)和普通类型(Regular Frame Type)。在特殊帧的帧类型字节中,同时包含了表示类型和一些flag的字段,而普通帧的帧类型字段的使用方式非常简。

现在定义的特殊帧类型包括:

      +------------------+-----------------------------+
      | Type-field value |     Control Frame-type      |
      +------------------+-----------------------------+
      |     1fdooossB    |  STREAM                     |
      |     01ntllmmB    |  ACK                        |
      |     001xxxxxB    |  CONGESTION_FEEDBACK        |
      +------------------+-----------------------------+

现在定义的普通帧类型包括:

      +------------------+-----------------------------+
      | Type-field value |     Control Frame-type      |
      +------------------+-----------------------------+
      | 00000000B (0x00) |  PADDING                    |
      | 00000001B (0x01) |  RST_STREAM                 |
      | 00000010B (0x02) |  CONNECTION_CLOSE           |
      | 00000011B (0x03) |  GOAWAY                     |
      | 00000100B (0x04) |  WINDOW_UPDATE              |
      | 00000101B (0x05) |  BLOCKED                    |
      | 00000110B (0x06) |  STOP_WAITING               |
      | 00000111B (0x07) |  PING                       |
      +------------------+-----------------------------+

6.2、STREAM帧

STREAM帧可以同时用来隐式的创建流和在上面发送数据,它有下面的格式:

     0        1       ...               SLEN
+--------+--------+--------+--------+--------+
|Type (8)| Stream ID (8, 16, 24, or 32 bits) |
|        |    (Variable length SLEN bytes)   |
+--------+--------+--------+--------+--------+

  SLEN+1  SLEN+2     ...                                         SLEN+OLEN
+--------+--------+--------+--------+--------+--------+--------+--------+
|   Offset (0, 16, 24, 32, 40, 48, 56, or 64 bits) (variable length)    |
|                    (Variable length: OLEN  bytes)                     |
+--------+--------+--------+--------+--------+--------+--------+--------+

  SLEN+OLEN+1   SLEN+OLEN+2
+-------------+-------------+
| Data length (0 or 16 bits)|
|  Optional(maybe 0 bytes)  |
+------------+--------------+

STREAM帧头部中的各个部分如下所示:

  • 帧类型(Frame Type):8个bit,包含了各种标志位(1fdooossB):

    • 最左边的置1表示这是一个STRAME帧。
    • "f"比特是FIN位,当设置为1,表式发送者已经完成了数据的发送。
    • 接下来的三位“ooo”表示Offset部分(这一部分携带什么信息?)的长度,分别为0,16,24,32,40,48,56或者64位长。
    • 两位“ss”表示Stream ID的长度,分别为8,16,24,32位长。
  • Stream ID:可变长度的Stream ID。

  • Offset:指明了当前数据部分的在数据流中的偏移量。

  • Data length:一个可选的无符号整数,表示了当前STREAM帧中数据部分的长度。只有当前是“full-sized“包时,才可以省略Data length。

一个STREAM帧要么有长度非0的数据,要么FIN设置为1。

8.3、ACK Frame

ACK帧用来通知对方收到了哪些packet,还有哪些packet是还没有收到的(这些被认为还没有收到的packet可能需要重发)。QUIC的ACK中包含了接收到的最大的packet number和一系列当前packet number下丢失的包的范围。To limit the NACK ranges to the ones that haven't yet been communicated to the peer(这句不是很懂),终端会周期性的发送STOP_WAITING帧,来表式不要继续等待小于一个特定number的packet,提高对方的“least unacked” packet number(这应该是作为返回ack帧时候的一个参数)。

帧的格式如下所示:

      0        1                           N
 +--------+--------+---------------------------------------------------+
 |  Type  |Received|                Largest Observed                   |
 |  (8)   |Entropy |              (8, 16, 32, or 48 bits)              |
 +--------+--------+---------------------------------------------------+

    N+1       N+2      N+3      N+4                   N+8
 +--------+--------+---------+--------+--------------------------------+
 |    Ack Delay    |   Num   | Delta  |        First Timestamp         |
 |     Time (16)   |Timestamp|Largest |           (32 bits)            |
 |                 |   (8)   |Observed|                                |
 +--------+--------+---------+--------+--------------------------------+

    N+9         N+11 - X
 +--------+-------------------+
 | Delta  |   Time Since      |
 |Largest | Previous Timestamp|  <-- Repeat (NumTimestamp - 1) times
 |Observed|     (16 bits)     |
 +--------+-------------------+

     X                        X+1 - Y                           Y+1
 +--------+-------------------------------------------------+--------+
 | Number |     Missing Packet Sequence Number Delta        | Range  |
 | Ranges |           (8, 16, 32, or 48 bits)               | Length |
 | (opt)  |        (repeats Number Ranges times)            |(Repeat)|
 +--------+-------------------------------------------------+--------+

     Y+2                       Y+3 - Z
 +--------+-----------------------------------------------------+
 | Number |            Revived Packet Number                    |
 | Revived|  (8, 16, 32, or 48 bits, same as Largest Observed)  |
 | (opt)  |        (repeats Number Revived times)               |
 +--------+-----------------------------------------------------+

ACK帧中的属性如下所示:

  • 帧类型:帧类型长度为8-bit,包含各种标志位(01nt11mmB).

    • 前两位必须设置为01,表示这是一个ACK帧。
    • “n”标志位表示后面是否有NACK范围(没收到的包)。
    • “t”标志位表示这个ACK帧是否被截断了。截断可能发生在一个QUIC包无法包含整个ACK帧的时候,或者NACK范围超过了最大的NACK范围(255)
    • 两个“11”标志位表示了Largest Observed feild的长度,分别为1,2,4或者6字节长度。
    • 两个“mm”标志位表示了Missing Packet Sequence Number Delta field的长度,分别为1,2,4或者6字节长度。
  • Received Entropy(熵):一个8bit的无符号整数,表示了所有收到的packet的熵的哈希值得累积(cumulative hash of entropy)。Entropy accumulation将会在后面讲解。

  • Largest Observed:是一个变长的无符号整数,表示收到的最大的packet编号。

    When an ACK frame is truncated, it indicates a packet number greater than the specified largest observed has been received, but information about those additional receptions can't fit into this frame (typically due to packet size restrictions).
  • Ack Delay Time:

    16-bit的无符号浮点数,表示从接收到编号最大的packet到当前ack帧发送出去的时间,单位是微秒。任何超过表示范围的时间都表示成0xFFFF;

  • Timestamp Section:(这部分包含了已经收到的包的信息)

    • Num Timestamp:长度为8-bit的无符号整数,表示ack帧将包含的timestamp数,在timestamps中,将会有这么多数量的 对。
    • Delta Largest Observed:8-bit的无符号整数,表示确认的packet编号和largest observed number之间的偏移量。所以packet number 等于 largest observed 减去 delta largest observed;
    • First Timestamp:32-bit的无符号数,表示从连接建立到接收到确认的包所经历的时间,单位是微秒。
    • Delta Largest Observed(上面的重复,一共重复Num Timestamp-1次)。
    • Time Since Previous Timestamp(重复多次):16-bit无符号浮点数,表示接收到当前确认的包相对于上一个确认的包的时间差,单位是微秒。
  • Missing Packet Section:

    • Num Ranges:一个可选的8-bit无符号整数,表示丢失包区间(missing packes ranges)的个数,范围是[least unacked, largest observed)。只有当“n”标志位设置为1时才存在。
    • Missing Packet Sequence Number Delta:长度可变的偏移量。简单点说,就是quic将还没有收到的数据转化为 <丢失数据的起始地址偏移量,丢失数据的长度>,这个数据对会重复Num Ranges次,第一次出现时,偏移量是相对Largest Observed Number,之后的偏移量都是相对于前一个数据对,如果偏移量为0,表示丢失数据的区间超过255,所以分为了两个连续的数据对表示。逻辑上和上面的Timestamp Section很相似。
    • Range Length:对应于上面的丢失数据的长度。
  • Revived Packet Section:格式和Timestamp Section 和 Missing Packet Section很相似,不同之处在于这里面记录了通过FEC恢复的包的信息。

8.3.1、Entropy Accumulation(TODO)

8.4、STOP_WAITING帧

​ STOP_WAITING帧的作用是通知对方不要继续等待编号低于一个特定值的包。这个特定值是边长的,长度和包头部中包编号的长度相同,可以是1,2,4或者6字节。

   0        1        2        3         4        5        6      7
 +--------+--------+--------+--------+--------+--------+-------+-------+
 |Type (8)|Sent    |   Least unacked delta (8, 16, 32, or 48 bits)     |
 |        |Entropy |                       (variable length)           |
 +--------+--------+--------+--------+--------+--------+-------+-------+

​ STOP_WAITING帧的属性如下所示:

  • 帧类型:8-bit,必须设置为0x06表示这是一个STOP_WAITING帧。
  • Sent Entropy:(TODO)
  • Least Unacked Delta:可变长的包序号差值(packet number delta), 用公共头中的包序号减去它可以得到最小的未确认号(the least unacked,这个结果是发送方等待的最小的ack确认号,如果接收方丢失了比这个结果更小值的包序号,接收方应该认为这些包不可撤销的丢失了。

8.5、WINDOW_UPDATE帧

WINDOW_UPDATE Frame用于通告对端接收窗口增加了,如果Stream ID 等于0,表示这是连接级别的流量控制,否则是流级别的流量控制,表示在指定的流上应该增加其流量控制窗口。帧格式如下:

       0         1                 4        5                 12
   +--------+--------+-- ... --+-------+--------+-- ... --+-------+
   |Type(8) |    Stream ID (32 bits)   |  Byte offset (64 bits)   |
   +--------+--------+-- ... --+-------+--------+-- ... --+-------+
  • Frame Type:8bit,必须设置成0x40,表示他是WINDOW_UPDATE Frame

  • Stream ID: 0表示连接级别的流量控制,非0 指定的流

  • Byte offset: 64bit 表示指定流上数据的绝对偏移。如果是连接级别的流量控制,表示所有流总数据的绝对偏移

byte offset 表示WINDOW_Update Frame 帧的接收方 在指定的stream 上只能发送这么多的数据,如果发多了,对端可能会关闭连接。如果再指定的stream id 上收到了多个WINDOW_UPDATE帧,接收方只需要记录最大最大byteoffset的帧。流和连接级别的流量控制窗口初始时都是16KB,在握手阶段会增加。在握手的过程中双方会协商SFCW(Stream Flow Control Wind) 和 CFCW(Connection Flow Control Wind)参数。

8.6、BLOCKED帧

BLOCK Frame 告诉对端,我有数据要发送,但是被阻塞了,它纯粹是一个通知信息帧,在调试的时候有用。接收方收到BLOCK Frame 后会打印一些日志信息,然后直接丢弃这个包。其帧结构如下:

      0        1        2        3         4
   +--------+--------+--------+--------+--------+
   |Type(8) |          Stream ID (32 bits)      |
   +--------+--------+--------+--------+--------+
  • Frame Type:8-bit,必须设置为0x05来表示这是一个BLOCKED帧
  • Stream ID:被阻塞的流的id;

8.7、CONGESTION_FEEDBACK帧

​ 这是一个正在试验中的帧类型,现在还没有正式使用。它的目的是为了提供除了标准ack帧以外的关于拥塞控制的额外信息。

8.8、PADDING帧

8.9、RST_STREAM帧

RST_STREAM Frame可以异常终止一条流,当该帧又流的创建者发送时,表示创建者希望取消这条流,当接收方发送该帧时,表示出现了错误,或者接收者不想接收这条流,应该这条流应该被关闭。帧格式如下:

     0        1            4      5              12     8             16
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+
|Type(8)| StreamID (32 bits) | Byte offset (64 bits)| Error code (32 bits)|
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+

RST_STREAM各个字段如下:

  • Frame Type:8-bit,必须设置为0x01表示这是一个RST_STREAM帧。
  • Stream ID:需要关闭的流的id;
  • Byte offset:64-bit长的无符号整数,表示这个流末尾的绝对偏移量;
  • Error code:32字节的QuicErrorCode,用来表示这个流关闭的原因。

8.10、PING帧

PING帧可以用来确定对方是否还存在。在链接建立之后,PING帧可以用来保持链接打开。默认情况下链接活动静默15秒钟之后会发送一个PING帧。

PING帧只有一个Frame Type字段,8-bit长,设置为0x07。

8.11、CONNECTION_CLOSE帧

CONNECTION_CLOSE帧可以用来通知对方链接已经关闭,如果还有一写流数据没有传输完成,这些流也会被隐式的关闭。(理想情况下,会首先发送一个GOAWAY帧,预留足够的时间让所有的流先关闭)。帧格式如下所示:

       0        1             4        5        6       7
   +--------+--------+-- ... -----+--------+--------+--------+----- ...
   |Type(8) | Error code (32 bits)| Reason phrase   |  Reason phrase
   |        |                     | length (16 bits)|(variable length)
   +--------+--------+-- ... -----+--------+--------+--------+----- ...
  • Frame Type:8-bit,设置为0x02
  • Error Code:32-bit,表示链接为什么关闭
  • Reason phrase length:16-bit,表示后面的Reason phrase的长度。可选的,如果发送者不想提供QuicErrorCode以外信息,那么这部分可以省略。
  • Reason phrase:使用文字表达的链接关闭的原因,可选的。

8.12、GOAWATY帧

GOAWAY帧可以用来通知对方链接应该停止使用,而且未来很有可能被中断。所有正在活动的流依旧会正常处理,但是GOAWAY帧的发送者不会再发起新的流,而且也不会接受新的流。GOAWAY帧格式如下:

        0        1             4      5       6       7      8
   +--------+--------+-- ... -----+-------+-------+-------+------+
   |Type(8) | Error code (32 bits)| Last Good Stream ID (32 bits)| ->
   +--------+--------+-- ... -----+-------+-------+-------+------+

         9        10       11
   +--------+--------+--------+----- ...
   | Reason phrase   |  Reason phrase
   | length (16 bits)|(variable length)
   +--------+--------+--------+----- ...
  • Frame Type:8-bit,设置为0x06表示这是一个GOAWAY帧。
  • Error Code:32-bit长的QuicErrorCode,表示链接关闭的原因;
  • Last Good Stream ID:GOAWAY帧发送者会接收的最后一个流的id;
  • Reason phrase和Reason phrase:同connection close帧;

​ 握手过程的职责是协商一系列QUIC链接的传输参数。

9.1、必须参数

  • SFCW——Stream Flow Control Window:流层面流量控制的窗口大小,单位是字节。
  • CFCW——Connection Flow Control Window:链接层面流量控制的窗口大小,单位是字节。

9.2、可选参数

  • SRBF——Socket receive buffer size in bytes:如果担心对方在从内核socket buffer读数据的过程中存在延迟现象,可以限制双方的CWND的最大值到一个和socket receive buffer相似的大小。
  • TCID——Connection ID truncation:表示支持截断的Connection ID。如果接收到对方的这个标志位,说明接下来发送个对方的数据包中Connection ID要被截断为0字节(也就是不发送)。可以在客户端的临时端口只用来建立一个链接到的时候使用。
  • COPT——Connection Option are a repeated tag field:这一部分包含了client或者server需要的链接建立选项。例如这里面可以包含拥塞控制算法的种类或者初始化窗口大小这样的参数。

QuicErrorCode到数字的映射关系现在定义在chromium源码文件src/net/quic/quic_protocol.h文件中