跳到主要内容

2. 压缩过程概述 (Compression Process Overview)

与 HPACK 一样, QPACK 使用两个表将 field lines ("headers") 与索引关联起来. static table (Section 3.1) 是预定义的, 包含常见的 header field lines (其中一些值为空). dynamic table (Section 3.2) 在连接过程中逐步构建, encoder 可用它索引已编码 field sections 中的 header 和 trailer field lines.

QPACK 定义了单向流, 用于从 encoder 向 decoder 发送指令, 以及反向发送指令.

2.1 Encoder

encoder 通过为列表中的每个 field line 发出 indexed representation 或 literal representation, 将 header section 或 trailer section 转换为一系列 representations; 见 Section 4.5. indexed representation 通过用 static table 或 dynamic table 的索引替换名称以及可能的值来实现高压缩率. 对 static table 的引用和 literal representation 不需要任何动态状态, 因而绝不会带来队头阻塞风险. 如果 encoder 尚未收到表示该条目已在 decoder 端可用的确认, 则对 dynamic table 的引用会有队头阻塞风险.

encoder MAY 将其选择的任何条目插入 dynamic table; 这不限于正在压缩的 field lines.

QPACK 保留每个 field section 内 field lines 的顺序. encoder MUST 按 field representations 在输入 field section 中出现的顺序发出它们.

QPACK 的设计将更复杂的状态跟踪限制在 encoder 侧, 使 decoder 相对简单.

2.1.1 Dynamic Table 插入限制 (Limits on Dynamic Table Insertions)

如果 dynamic table 包含无法驱逐的条目, 则可能无法向其中插入条目.

dynamic table 条目在插入后不能立即被驱逐, 即使它从未被引用. 一旦 dynamic table 条目的插入得到确认, 并且未确认 representations 中不再存在对该条目的未完成引用, 该条目就变为可驱逐. 注意, encoder stream 上的引用永远不会阻止条目被驱逐, 因为这些引用保证会在驱逐该条目的指令之前被处理.

如果 dynamic table 没有足够空间容纳新条目而不驱逐其他条目, 且将被驱逐的条目不可驱逐, encoder MUST NOT 将该条目插入 dynamic table (包括现有条目的副本). 为避免这种情况, 使用 dynamic table 的 encoder 必须跟踪每个 field section 引用的每个 dynamic table 条目, 直到这些 representations 被 decoder 确认; 见 Section 4.4.1.

2.1.1.1 避免被禁止的插入 (Avoiding Prohibited Insertions)

为确保 encoder 不会被阻止添加新条目, encoder 可以避免引用接近被驱逐的条目. encoder 可以发出 Duplicate 指令 (Section 4.3.4) 并引用该副本, 而不是引用这样的旧条目.

哪些条目因过于接近驱逐而不宜引用, 由 encoder 自行决定. 一种启发式方法是在 dynamic table 中保持固定数量的可用空间: 可以是未使用空间, 也可以是通过驱逐非阻塞条目而可回收的空间. 为此, encoder 可以维护一个 draining index, 即 dynamic table 中它将发出引用的最小 absolute index (Section 3.2.4). 随着新条目插入, encoder 增大 draining index, 以维持表中不会被引用的区域. 如果 encoder 不再为 absolute index 低于 draining index 的条目创建新引用, 这些条目的未确认引用数最终会变为零, 从而允许它们被驱逐.

            `<-- Newer Entries          Older Entries -->`
(Larger Indices) (Smaller Indices)
+--------+---------------------------------+----------+
| Unused | Referenceable | Draining |
| Space | Entries | Entries |
+--------+---------------------------------+----------+
^ ^ ^
| | |
Insertion Point Draining Index Dropping
Point

Figure 1: Draining Dynamic Table Entries

2.1.2 被阻塞的流 (Blocked Streams)

由于 QUIC 不保证不同流上的数据顺序, decoder 可能遇到引用尚未收到的 dynamic table 条目的 representations.

每个已编码 field section 都包含 Required Insert Count (Section 4.5.1), 即能够解码该 field section 的最低 Insert Count 值. 对于使用 dynamic table 引用编码的 field section, Required Insert Count 等于所有被引用 dynamic table 条目的最大 absolute index 加一. 对于不引用 dynamic table 编码的 field section, Required Insert Count 为零.

当 decoder 收到 Required Insert Count 大于自身 Insert Count 的已编码 field section 时, 该流无法立即处理, 并被视为 "blocked"; 见 Section 2.2.1.

decoder 使用 SETTINGS_QPACK_BLOCKED_STREAMS 设置指定可被阻塞流数量的上限; 见 Section 5. encoder MUST 始终将可能变为 blocked 的流数量限制在 SETTINGS_QPACK_BLOCKED_STREAMS 的值以内. 如果 decoder 遇到的 blocked streams 数量超过它承诺支持的数量, 它 MUST 将此视为 QPACK_DECOMPRESSION_FAILED 类型的连接错误.

注意, decoder 未必会在每个存在阻塞风险的流上实际被阻塞.

encoder 可以决定是否承担流变为 blocked 的风险. 如果 SETTINGS_QPACK_BLOCKED_STREAMS 的值允许, 引用仍在传输中的 dynamic table 条目通常可以提升压缩效率; 但如果发生丢包或重排序, 该流可能会在 decoder 端被阻塞. encoder 可以只引用已确认的 dynamic table 条目来避免阻塞风险, 但这意味着需要使用 literals. 由于 literals 会使已编码 field section 更大, 这可能导致 encoder 受拥塞或流量控制限制而阻塞.

2.1.3 避免流量控制死锁 (Avoiding Flow-Control Deadlocks)

在受流量控制约束的流上写入指令可能产生死锁.

decoder 可能停止向承载已编码 field section 的流授予流量控制额度, 直到在 encoder stream 上收到必要更新. 如果向 encoder stream (或整个连接) 授予流量控制额度取决于承载已编码 field sections 的流上数据被消费并释放, 就可能产生死锁.

更一般地说, 如果 decoder 在完整收到指令之前扣留流量控制额度, 包含大型指令的流可能陷入死锁.

为避免这些死锁, 除非有足够的流级和连接级流量控制额度可用于整个指令, encoder SHOULD NOT 写入该指令.

2.1.4 Known Received Count

Known Received Count 是 decoder 已确认的 dynamic table 插入和复制总数. encoder 跟踪 Known Received Count, 以识别哪些 dynamic table 条目可被引用而不会潜在地阻塞流. decoder 跟踪 Known Received Count, 以便能够发送 Insert Count Increment 指令.

Section Acknowledgment 指令 (Section 4.4.1) 表明 decoder 已收到解码该 field section 所需的全部 dynamic table 状态. 如果已确认 field section 的 Required Insert Count 大于当前 Known Received Count, 则将 Known Received Count 更新为该 Required Insert Count 值.

Insert Count Increment 指令 (Section 4.4.3) 按其 Increment 参数增加 Known Received Count. 相关指南见 Section 2.2.2.3.

2.2 Decoder

与 HPACK 中一样, decoder 处理一系列 representations 并发出相应的 field section. 它还会处理在 encoder stream 上收到的、用于修改 dynamic table 的指令. 注意, 已编码 field sections 和 encoder stream 指令到达于不同的流. 这不同于 HPACK, 在 HPACK 中, 已编码 field sections (header blocks) 可以包含修改 dynamic table 的指令, 且不存在专用的 HPACK 指令流.

decoder MUST 按 representations 在已编码 field section 中出现的顺序发出 field lines.

2.2.1 阻塞解码 (Blocked Decoding)

收到已编码 field section 后, decoder 检查 Required Insert Count. 当 Required Insert Count 小于或等于 decoder 的 Insert Count 时, 该 field section 可以立即处理. 否则, 接收该 field section 的流变为 blocked.

在 blocked 期间, 已编码 field section 数据 SHOULD 保留在 blocked stream 的流量控制窗口中. 当 Insert Count 大于或等于 decoder 已开始从该流读取的所有已编码 field sections 的 Required Insert Count 时, 该流解除阻塞.

处理已编码 field sections 时, decoder 预期 Required Insert Count 等于能够解码该 field section 的最低 Insert Count, 如 Section 2.1.2 所规定. 如果遇到小于预期的 Required Insert Count, 它 MUST 将此视为 QPACK_DECOMPRESSION_FAILED 类型的连接错误; 见 Section 2.2.3. 如果遇到大于预期的 Required Insert Count, 它 MAY 将此视为 QPACK_DECOMPRESSION_FAILED 类型的连接错误.

2.2.2 状态同步 (State Synchronization)

decoder 通过在 decoder stream 上发出 decoder instructions (Section 4.4) 来通告以下事件.

2.2.2.1 Field Section 处理完成 (Completed Processing of a Field Section)

当 decoder 完成对使用包含 dynamic table 引用的 representations 编码的 field section 的解码后, 它 MUST 发出 Section Acknowledgment 指令 (Section 4.4.1). 在中间响应, trailers 和 pushed requests 的情况下, 一个流可以承载多个 field sections. encoder 将每个 Section Acknowledgment 指令解释为确认给定流上最早的、尚未确认且包含 dynamic table 引用的 field section.

2.2.2.2 放弃流 (Abandonment of a Stream)

当 endpoint 在流结束之前或该流上的所有已编码 field sections 处理完成之前收到流重置, 或放弃读取某个流时, 它会生成 Stream Cancellation 指令; 见 Section 4.4.2. 这向 encoder 表明该流上对 dynamic table 的所有引用都不再未完成. 如果 decoder 的 maximum dynamic table capacity (Section 3.2.3) 等于零, 它 MAY 省略发送 Stream Cancellations, 因为 encoder 不可能有任何 dynamic table 引用. encoder 不能从该指令推断 dynamic table 的任何更新已经被收到.

Section Acknowledgment 和 Stream Cancellation 指令允许 encoder 移除对 dynamic table 条目的引用. 当 absolute index 低于 Known Received Count 的某个条目引用数为零时, 该条目被视为可驱逐; 见 Section 2.1.1.

2.2.2.3 新表条目 (New Table Entries)

在 encoder stream 上收到新表条目后, decoder 选择何时发出 Insert Count Increment 指令; 见 Section 4.4.3. 每添加一个新的 dynamic table 条目后就发出此指令, 可以向 encoder 提供最及时的反馈, 但也可能与其他 decoder 反馈重复. 通过延迟 Insert Count Increment 指令, decoder 可能合并多个 Insert Count Increment 指令, 或完全用 Section Acknowledgments 替代它们; 见 Section 4.4.1. 但是, 如果 encoder 等待条目被确认后才使用它, 延迟过久可能导致压缩效率降低.

2.2.3 无效引用 (Invalid References)

如果 decoder 在 field line representation 中遇到对 dynamic table 条目的引用, 而该条目已经被驱逐, 或其 absolute index 大于或等于声明的 Required Insert Count (Section 4.5.1), 它 MUST 将此视为 QPACK_DECOMPRESSION_FAILED 类型的连接错误.

如果 decoder 在 encoder instruction 中遇到对已经被驱逐的 dynamic table 条目的引用, 它 MUST 将此视为 QPACK_ENCODER_STREAM_ERROR 类型的连接错误.