WebSocket 协议详解
1. WebSocket 协议的帧数据详解
1.1 帧结构
0 1 2 30 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 ... |+---------------------------------------------------------------+
WebSocket 客户端与服务器通信的最小单位是帧(frame),一条完整消息由一个或多个帧组成:
- 发送方将消息切割为多个帧发送
- 接收方接收到消息帧后重新组装为完整消息
- 接收方根据 FIN 标识判断是否接收到消息的最后一个数据帧
帧基本结构
帧由多个字段顺序排列构成,字段说明如下:
字段 | 长度 | 描述 |
---|---|---|
FIN | 1 位 | 表示该帧是否为消息的最后一帧1 :最后一帧,0 :还有后续帧 |
RSV1 - RSV3 | 各 1 位 | 保留位,默认值为 0 。需在握手阶段协商使用 |
Opcode | 4 位 | 定义帧的操作类型(文本/二进制/控制帧等) |
Mask | 1 位 | 指示负载数据是否经过掩码处理 客户端发送必须掩码,服务器发送不需掩码 |
Payload length | 7 位/7+16位/7+64位 | 负载数据长度编码0-125 :直接使用7位126 :后接2字节表示长度127 :后接8字节表示长度 |
Masking-key | 0 或 4 字节 | 当 Mask=1 时存在,用于负载数据掩码处理 |
Payload data | 变长 | 实际传输的数据 |
字段详细解释
-
FIN 字段
- 标识当前帧是否为消息的最后一帧
1
:消息结束帧;0
:消息还有后续帧
-
RSV1-RSV3 字段
- 保留位,为协议扩展预留
- 默认值必须为
0
,否则接收方应断开连接
-
Opcode 字段
值 类型 描述 0x0 延续帧 继续未完成的消息 0x1 文本帧 UTF-8 编码文本 0x2 二进制帧 二进制数据 0x3-0x7 保留帧 未来非控制帧扩展 0x8 关闭帧 关闭连接 0x9 Ping帧 心跳检测 0xA Pong帧 对Ping的响应 0xB-0xF 保留帧 未来控制帧扩展 -
Mask 字段
- 客户端发送必须设为
1
(掩码处理) - 服务器发送必须设为
0
(无掩码)
- 客户端发送必须设为
-
Payload length 字段
- 采用可变长度编码适应不同数据量
- 长度在0-125字节时直接使用7位表示
-
Masking-key 字段
- 当 Mask=1 时存在(4字节随机密钥)
- 掩码算法:
C[i] = P[i] ^ M[i % 4]
- 接收方使用相同密钥进行解掩码
示例:文本帧 "Hello"FIN: 1 (单帧消息) RSV: 000 Opcode: 0001 (文本帧) Mask: 1 (客户端发送) Payload length: 5 (0000101) Masking-key: 0x12345678 Payload data: "Hello" 掩码后值
1.2 生成数据帧
消息分片机制
目的:
- 支持传输未知长度的超大数据
- 实现流式传输(边生成边发送)
- 避免大数据一次性加载到内存
分片规则
帧类型 | FIN | Opcode | 描述 |
---|---|---|---|
起始帧 | 0 | ≠0 | 消息的第一帧 |
中间帧 | 0 | 0 | 消息的中间部分 |
结束帧 | 1 | 0 | 消息的最后一帧 |
重要规则:
- 控制帧不允许分片(包括关闭帧/Ping/Pong)
- 控制帧可穿插在分片消息中传输
- 组成消息的所有帧必须是相同数据类型
- 消息碎片类型只能是文本或二进制
2. WebSocket 协议控制帧结构详解
控制帧操作码:0x08
(关闭)、0x09
(Ping)、0x0A
(Pong)
重要特性:
- 所有控制帧负载长度 ≤125 字节
- 控制帧禁止分片处理
- 控制帧可穿插在消息片之间传输
2.1 关闭帧(Opcode 0x08)
功能:正常关闭连接或指示关闭原因
帧结构要求:
- 客户端发送必须掩码处理
- 数据部分(若存在)前2字节为无符号整数(状态码)
- 可选UTF-8编码的关闭原因(人类不可读)
关闭流程
- 主动关闭方:发送关闭帧 → 不再发送任何数据
- 被动接收方:收到关闭帧后必须响应关闭帧
- 双方关闭:交换关闭帧后关闭TCP连接
- 超时处理:服务器应立即关闭TCP连接;客户端可等待或超时关闭
示例:正常关闭(状态码1000)FIN: 1
RSV: 000
Opcode: 1000 (关闭帧)
Mask: 1 (客户端发送)
Payload length: 2
Payload data: 0x03E8 (1000的16进制)
2.2 Ping帧(Opcode 0x09)
功能:连接状态检测(心跳检测)
重要规则:
- 可在连接建立后任意时间发送
- 必须包含应用数据(最多125字节)
- 接收方收到后必须返回Pong帧(除非已收到关闭帧)
- 响应时间:尽快返回Pong帧
示例:带自定义数据的PingFIN: 1
RSV: 000
Opcode: 1001 (Ping帧)
Mask: 1 (客户端发送)
Payload length: 9
Payload data: "HEARTBEAT"
2.3 Pong帧(Opcode 0x0A)
功能:对Ping帧的响应
重要规则:
- 必须包含与对应Ping完全相同的应用数据
- 对于连续多个Ping,只需响应最后一个
- 可主动发送(作为单向心跳)
- 对主动发送的Pong帧不需要响应
高级行为
- 数据一致性:必须完全复制Ping的Payload数据
- 延迟响应:可在处理完当前消息后发送
- 流控机制:不可用于流量控制
3. WebSocket 心跳机制
机制核心
关键规则
- 触发条件:任何一端收到Ping帧
- 响应要求:必须立即返回Pong帧(相同Payload数据)
- 唯一例外:当连接正处于关闭状态时可不响应
- Pong优先级:高于普通数据帧处理
应用场景
- 连接保活:防止中间设备(NAT/防火墙)断开空闲连接
- 状态检测:确认对方是否在线/响应
- 网络诊断:通过计算Ping-Pong延时(RTT)测量网络质量
- 双向验证:确保连接双向通信能力
高级实现技巧
- Payload设计:包含时间戳(计算RTT)和序列号(匹配请求响应)
- 超时机制:Ping发送方应实现响应超时检测
- 频率控制:推荐间隔15-60秒(视网络环境调整)
- 错误处理:连续多次超时后标记连接不可用