5. Data Framing
WebSocket 协议使用 frame (帧) 来传输数据. 本章定义 WebSocket frame 的格式和处理规则.
5.1 概览
一旦 WebSocket 连接建立, 客户端和服务器就可以双向传输数据. 数据以一系列 frame 的形式传输.
关键概念:
- Frame (帧): 基本传输单元, 包含 header 和 payload
- Message (消息): 应用层数据, 可以由一个或多个 frame 组成
- Fragmentation (分片): 大消息可以在多个 frame 中发送
5.2 基本成帧协议
Frame 结构
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
字段说明
FIN (1 bit)
0: 这不是消息的最后一个 frame (后面还有更多 frame)1: 这是消息的最后一个 frame (或唯一的 frame)
消息分片示例:
Frame 1: FIN=0, Opcode=0x1 (Text), Data="Hello "
Frame 2: FIN=0, Opcode=0x0 (Continuation), Data="World"
Frame 3: FIN=1, Opcode=0x0 (Continuation), Data="!"
完整消息: "Hello World!"
RSV1, RSV2, RSV3 (各 1 bit)
- 为扩展保留
- 如果未协商扩展, MUST 为 0
- 如果在没有已定义扩展的情况下收到非零值, MUST 关闭连接
Opcode (4 bits)
定义 frame 类型:
| Opcode | 类型 | 描述 |
|---|---|---|
0x0 | Continuation | Continuation frame (分片消息的后续 frame) |
0x1 | Text | Text frame (UTF-8 编码) |
0x2 | Binary | Binary frame |
0x3-0x7 | - | 保留 (data frame) |
0x8 | Close | Close frame |
0x9 | Ping | Ping frame |
0xA | Pong | Pong frame |
0xB-0xF | - | 保留 (control frame) |
MASK (1 bit)
- 客户端到服务器: MUST 为 1
- 服务器到客户端: MUST 为 0
如果 MASK=1, Payload Data 必须使用 Masking-key 进行 mask.
Payload Length (7 bits, 7+16 bits, 或 7+64 bits)
Payload length 编码:
- 0-125: 这就是实际长度
- 126: 后续 16 bits (2 bytes) 为实际长度 (network byte order)
- 127: 后续 64 bits (8 bytes) 为实际长度 (network byte order)
示例:
Payload length = 100 bytes
-> Payload len = 100 (直接编码)
Payload length = 1000 bytes
-> Payload len = 126
-> Extended payload length = 1000 (16-bit)
Payload length = 100000 bytes
-> Payload len = 127
-> Extended payload length = 100000 (64-bit)
Masking-key (0 或 4 bytes)
如果 MASK=1, 包含 32 bits (4 bytes) 的 masking key.
Payload Data (x+y bytes)
Payload data = Extension Data + Application Data
- Extension Data: 长度为 x, 由扩展协商决定, 默认 0
- Application Data: 长度为 y, 实际应用数据
5.3 客户端到服务器 Masking
为什么需要 Masking?
安全原因: 防止缓存投毒攻击. 某些中间代理可能会错误地缓存 WebSocket frame; masking 可确保数据不可预测.
Masking 算法
客户端 MUST 使用以下算法 mask 发送到服务器的所有 frame:
1. 生成 32-bit 随机 masking key
2. 将 masking key 放入 frame header 的 Masking-key 字段
3. 对 Payload Data 的每个 byte 应用 mask:
transformed-octet-i = original-octet-i XOR masking-key[i MOD 4]
算法实现 (JavaScript):
function maskData(data, maskingKey) {
const masked = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
masked[i] = data[i] ^ maskingKey[i % 4];
}
return masked;
}
// Example
const data = Buffer.from('Hello');
const maskingKey = Buffer.from([0x37, 0xfa, 0x21, 0x3d]);
const masked = maskData(data, maskingKey);
// Decode (using same algorithm)
const unmasked = maskData(masked, maskingKey); // 'Hello'
要点:
- XOR 是自反的:
(A XOR B) XOR B = A - 服务器使用相同算法解码
- 每个发送的 frame 必须使用新的随机 key
5.4 分片
大消息可以在多个 frame 中发送.
分片规则
- 第一个 frame: FIN=0, Opcode=data type (0x1 或 0x2)
- 中间 frame: FIN=0, Opcode=0x0 (Continuation)
- 最后一个 frame: FIN=1, Opcode=0x0 (Continuation)
分片示例
用三个 frame 发送消息 "Hello World!":
Frame 1:
FIN = 0
Opcode = 0x1 (Text)
Payload = "Hello "
Frame 2:
FIN = 0
Opcode = 0x0 (Continuation)
Payload = "World"
Frame 3:
FIN = 1
Opcode = 0x0 (Continuation)
Payload = "!"
分片约束
- Control frame (Close, Ping, Pong) MUST NOT 被分片
- Control frame 可以插入在分片 data frame 之间
- 片段必须按顺序发送和接收
5.5 控制帧
Control frame 用于传达连接状态. Opcode 范围: 0x8-0xF.
5.5.1 Close
- Opcode:
0x8 - 可以包含关闭码和原因
- 详情见第 7 章
5.5.2 Ping
- Opcode:
0x9 - 用途: 心跳检测, 检查连接是否存活
- 可以携带应用数据 (最大 125 bytes)
- 接收方 MUST 以 Pong frame 响应
Client -> Server: Ping (Opcode=0x9)
Server -> Client: Pong (Opcode=0xA, same Payload)
5.5.3 Pong
- Opcode:
0xA - 用途: 响应 Ping frame
- MUST 包含与 Ping frame 相同的 Payload
- 也可以主动发送 (未经请求的心跳)
Control Frame 规则
- 最大 Payload length: 125 bytes
- MUST NOT 被分片
- 可以插入在分片 data frame 之间
5.6 数据帧
Data frame 传输应用数据或扩展数据. Opcode 范围: 0x0-0x2, 0x3-0x7 保留.
Text Frame
- Opcode:
0x1 - Payload MUST 是有效的 UTF-8 编码文本
- 如果收到无效 UTF-8, MUST 关闭连接
Binary Frame
- Opcode:
0x2 - Payload 可以是任意二进制数据
- 由应用层负责解释
5.7 示例
单个未 Mask 的 Text Frame
0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f
│ │ └─────────┬────────────┘
│ │ └─ "Hello" (5 bytes)
│ └─ Payload len = 5
└─ FIN=1, Opcode=0x1 (Text)
单个 Masked Text Frame
0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58
│ │ └─────┬────────┘ └──────┬───────────┘
│ │ │ └─ Masked "Hello"
│ │ └─ Masking key
│ └─ MASK=1, Payload len = 5
└─ FIN=1, Opcode=0x1 (Text)
分片消息
Frame 1: 0x01 0x03 0x48 0x65 0x6c // FIN=0, Text, "Hel"
Frame 2: 0x80 0x02 0x6c 0x6f // FIN=1, Continuation, "lo"
完整消息: "Hello"
Ping Frame
0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f
│ │ └─────────┬────────────┘
│ │ └─ "Hello" (optional data)
│ └─ Payload len = 5
└─ FIN=1, Opcode=0x9 (Ping)
5.8 可扩展性
协议可以通过以下方式扩展:
- Opcode: 0x3-0x7 和 0xB-0xF 保留供将来使用
- RSV bits: 保留供扩展使用
- Extension Data: 扩展可以在 Payload 前添加数据
扩展必须通过握手协商 (Sec-WebSocket-Extensions).
参考链接
- 上一章: 4. Opening Handshake
- 下一章: 6. 发送和接收数据
- 详细说明: WebSocket Frame 结构细节