跳到主要内容

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类型描述
0x0ContinuationContinuation frame (分片消息的后续 frame)
0x1TextText frame (UTF-8 编码)
0x2BinaryBinary frame
0x3-0x7-保留 (data frame)
0x8CloseClose frame
0x9PingPing frame
0xAPongPong 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 中发送.

分片规则

  1. 第一个 frame: FIN=0, Opcode=data type (0x1 或 0x2)
  2. 中间 frame: FIN=0, Opcode=0x0 (Continuation)
  3. 最后一个 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 规则

  1. 最大 Payload length: 125 bytes
  2. MUST NOT 被分片
  3. 可以插入在分片 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 可扩展性

协议可以通过以下方式扩展:

  1. Opcode: 0x3-0x7 和 0xB-0xF 保留供将来使用
  2. RSV bits: 保留供扩展使用
  3. Extension Data: 扩展可以在 Payload 前添加数据

扩展必须通过握手协商 (Sec-WebSocket-Extensions).

参考链接