2. Streams (流)
QUIC中的流为应用程序提供了轻量级的、有序的字节流抽象 (Ordered Byte-Stream Abstraction)。流可以是单向的 (Unidirectional) 或双向的 (Bidirectional)。
流可以通过发送数据来创建。与流管理相关的其他过程——结束、取消和管理流量控制——都设计为施加最小的开销。例如,单个STREAM帧 (第19.8节) 可以打开、承载数据并关闭一个流。流也可以是长期存在的,可以持续整个连接的持续时间。
流可以由任一端点创建,可以与其他流交错并发发送数据,并且可以被取消。QUIC不提供任何方法来确保不同流上的字节之间的顺序。
QUIC允许任意数量的流并发运行,并允许在任何流上发送任意数量的数据,但受流量控制约束和流限制的约束;参见第4节。
2.1. Stream Types and Identifiers (流类型和标识符)
流可以是单向的或双向的。单向流在一个方向上传输数据:从流的发起方到其对等方。双向流允许双向发送数据。
流在连接内通过数值进行标识,称为流ID (Stream ID)。流ID是一个62位整数 (0到2^62-1),在连接上的所有流中是唯一的。流ID编码为可变长度整数;参见第16节。QUIC端点不得 (MUST NOT) 在连接内重用流ID。
流ID编码规则
流ID的最低有效位 (0x01) 标识流的发起方:
- 客户端发起的流: 流ID为偶数 (该位设为0)
- 服务器发起的流: 流ID为奇数 (该位设为1)
流ID的第二最低有效位 (0x02) 区分:
- 双向流: 该位设为0
- 单向流: 该位设为1
流类型表
因此,流ID的两个最低有效位将流标识为四种类型之一,如表1所示:
| 位值 | 流类型 |
|---|---|
| 0x00 | 客户端发起的双向流 (Client-Initiated, Bidirectional) |
| 0x01 | 服务器发起的双向流 (Server-Initiated, Bidirectional) |
| 0x02 | 客户端发起的单向流 (Client-Initiated, Unidirectional) |
| 0x03 | 服务器发起的单向流 (Server-Initiated, Unidirectional) |
表1: 流ID类型
流ID分配规则
每种类型的流空间从最小值开始 (分别为0x00到0x03);每种类型的后续流使用数值递增的流ID创建。如果流ID被无序使用,将导致该类型的所有具有较低编号流ID的流也被打开。
2.2. Sending and Receiving Data (发送和接收数据)
STREAM帧 (第19.8节) 封装应用程序发送的数据。端点使用STREAM帧中的流ID (Stream ID) 和偏移量 (Offset) 字段按顺序放置数据。
端点必须 (MUST) 能够将流数据作为有序字节流传递给应用程序。传递有序字节流要求端点缓冲任何无序接收的数据,直到通告的流量控制限制。
数据传递选项
QUIC没有对无序传递流数据做出特定的规定。但是,实现可以 (MAY) 选择提供将数据无序传递给接收应用程序的能力。
数据重复和一致性
端点可能在同一流偏移量处多次接收流的数据。已经接收的数据可以被丢弃。如果多次发送,给定偏移量处的数据不得 (MUST NOT) 改变;端点可以 (MAY) 将在流内的同一偏移量处接收到不同数据视为类型为PROTOCOL_VIOLATION的连接错误。
帧边界
流是有序的字节流抽象,对QUIC来说没有其他可见的结构。当数据被传输、在数据包丢失后重传或在接收方传递给应用程序时,预期不会保留STREAM帧边界。
流量控制
端点不得 (MUST NOT) 在任何流上发送数据,除非确保它在其对等方设置的流量控制限制内。流量控制在第4节中详细描述。
2.3. Stream Prioritization (流优先级)
如果为流分配的资源得到正确的优先级排序,流多路复用可以对应用程序性能产生重大影响。
QUIC不提供交换优先级信息的机制。相反,它依赖于从应用程序接收优先级信息。
QUIC实现应该 (SHOULD) 提供应用程序可以指示流的相对优先级的方法。实现使用应用程序提供的信息来确定如何为活动流分配资源。
优先级机制说明
与HTTP/2的区别:
- HTTP/2在协议层定义了优先级树 (Priority Tree)
- QUIC将优先级决策权交给应用层
- 这提供了更大的灵活性,但也需要应用协议自行设计优先级方案
2.4. Operations on Streams (流上的操作)
本文档没有为QUIC定义API;相反,它定义了应用协议可以依赖的一组流函数。应用协议可以假设QUIC实现提供的接口包括本节中描述的操作。为特定应用协议设计的实现可能只提供该协议使用的那些操作。
发送端操作
在流的发送端,应用协议可以:
写入数据 (Write Data)
了解流量控制信用 (第4.1节) 何时成功保留以发送写入的数据
结束流 (End Stream) - 干净终止
导致发送带有FIN位设置的STREAM帧 (第19.8节)
重置流 (Reset Stream) - 突然终止
如果流尚未处于终止状态,则导致发送RESET_STREAM帧 (第19.4节)
接收端操作
在流的接收端,应用协议可以:
读取数据 (Read Data)
从流中读取接收到的数据
中止读取 (Abort Reading)
中止流的读取并请求关闭,可能导致发送STOP_SENDING帧 (第19.5节)
状态通知
应用协议还可以请求通知流上的状态变化,包括:
- 对等方何时打开或重置流
- 对等方何时中止在流上的读取
- 何时有新数据可用
- 由于流量控制,何时可以或不能将数据写入流
💡 流机制核心要点
流的四种类型
客户端发起 服务器发起
↓ ↓
┌─────────┐ ┌─────────┐
│ 双向流 │ (ID: 0,4,8..)│ 双向流 │ (ID: 1,5,9..)
│ 0x00 │ │ 0x01 │
└─────────┘ └─────────┘
┌─────────┐ ┌─────────┐
│ 单向流 │ (ID: 2,6,10..)│ 单向流 │ (ID: 3,7,11..)
│ 0x02 │ │ 0x03 │
└─────────┘ └─────────┘
流的生命周期
创建 → 发送数据 → 结束/重置 → 关闭
↑ ↓
└─ 流量控制 ←┘
关键特性
- 轻量级: 单个帧可以打开、传输、关闭流
- 并发: 任意数量的流可以同时活动
- 独立: 流之间不保证顺序,避免队头阻塞
- 灵活: 可以长期存在或短暂使用
与TCP对比
| 特性 | QUIC流 | TCP |
|---|---|---|
| 多路复用 | ✅ 原生支持多流 | ❌ 单字节流 |
| 队头阻塞 | ✅ 流级别独立 | ❌ 全局阻塞 |
| 流创建开销 | 极低 | N/A |
| 优先级控制 | 应用层决定 | 无 |
下一章: 3. Stream States (流状态) - 详细的流状态机模型