WebSocket协议使用帧(Frame)来传输数据,每个帧都有特定的格式和用途。
帧的基本结构
WebSocket帧由多个字段组成,格式如下:
shell0 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)
- 标识是否为消息的最后一个分片
- 1:表示这是最后一个分片
- 0:表示后面还有分片
RSV1, RSV2, RSV3 (各1 bit)
- 保留位,必须为0
- 除非扩展协商了非零值
Opcode (4 bits)
定义帧的操作类型:
| Opcode | 含义 |
|---|---|
| 0x0 | 继续帧 |
| 0x1 | 文本帧 |
| 0x2 | 二进制帧 |
| 0x8 | 关闭连接 |
| 0x9 | Ping |
| 0xA | Pong |
| 0xB-F | 保留用于扩展 |
MASK (1 bit)
- 标识Payload Data是否使用掩码
- 客户端发送的帧必须为1
- 服务器发送的帧必须为0
Payload Length (7 bits, 7+16 bits, or 7+64 bits)
负载长度,三种编码方式:
- 0-125:直接表示长度
- 126:后续2字节表示长度(16位无符号整数)
- 127:后续8字节表示长度(64位无符号整数)
Masking-key (32 bits)
- 当MASK=1时存在
- 用于解码Payload Data
- 客户端必须使用随机掩码
Payload Data
实际传输的数据,如果使用掩码,需要解码
掩码算法
javascriptfunction unmask(maskedData, maskingKey) { const result = new Uint8Array(maskedData.length); for (let i = 0; i < maskedData.length; i++) { result[i] = maskedData[i] ^ maskingKey[i % 4]; } return result; }
消息分片
大消息可以分成多个帧传输:
- 第一个帧:FIN=0,Opcode!=0
- 中间帧:FIN=0,Opcode=0
- 最后一个帧:FIN=1,Opcode=0
关闭帧
关闭连接时发送关闭帧:
- Opcode=0x8
- Payload包含状态码(2字节)和可选的关闭原因