客户端发帧必须解掩码,否则解析结果全错:RFC 6455强制要求客户端帧Mask=1且带4字节Masking-key,服务端须校验掩码存在性与长度合法性,解码时须逐字节按i%4循环异或,不可批量或越界操作。

客户端发帧必须解掩码,否则解析结果全错
WebSocket协议强制要求:所有从客户端发往服务端的帧,Mask位必须为1,并携带4字节Masking-key。服务端收到后若发现Mask == 0,必须立即断连;若Mask == 1但没读到4字节掩码键,或后续数据长度与声明不符,同样要拒收。这不是可选行为,是RFC 6455第5.3节的硬性规定。
Masking-key在帧头中的位置和读取顺序
掩码键紧接在帧头长度字段之后,仅当Mask == 1时存在。帧头最小2字节,但实际长度取决于Payload length字段值:
- 若
Payload length∈ [0, 125]:帧头共2字节,掩码键从第3字节开始(索引2) - 若
Payload length == 126:帧头共4字节(含2字节扩展长度),掩码键从第5字节开始(索引4) - 若
Payload length == 127:帧头共10字节(含8字节扩展长度),掩码键从第11字节开始(索引10)
别硬记偏移量——直接按规范逻辑走:read header → extract mask bit → if masked, read next 4 bytes as maskKey。用std::vector<uint8_t></uint8_t>缓存已读字节,用下标访问比指针算术更安全。
解掩码操作必须逐字节异或,不能memcpy后批量处理
掩码不是加密,是简单XOR:对载荷中每个字节payload[i],执行payload[i] ^= maskKey[i % 4]。常见错误包括:
立即学习“C++免费学习笔记(深入)”;
- 把
maskKey当成大端整数一次性异或(如*((uint32_t*)payload) ^= *(uint32_t*)maskKey)→ 字节序错、越界、未对齐崩溃 - 忽略
i % 4循环,用固定maskKey[0]扫全段 → 解出来的全是乱码 - 在未校验
payloadLen合法性前就分配缓冲区 → 可能触发std::bad_alloc或整数溢出
正确做法示例:
for (size_t i = 0; i < payloadLen; ++i) {
payload[i] ^= maskKey[i & 0x3]; // i % 4 等价于 i & 0x3,更快
}
服务端收帧时掩码只用于解包,发帧时绝不加掩码
这是单向约束:只有客户端→服务端方向强制掩码;服务端→客户端方向禁止掩码。如果你在服务端代码里看到sendFrame(..., mask: true),立刻删掉——RFC明确要求服务端发帧时Mask位必须为0,否则浏览器会静默关闭连接,且不报任何JS错误。
另外注意:Wslay等C库的wslay_frame_send默认不掩码,而wslay_frame_recv要求你传入mask_key参数做解包;C++手写实现时,务必在parseFrame()入口处先检查header[1] & 0x80,再决定是否读掩码键——漏掉这步,弱网下偶发的粘包帧会直接导致后续全部解析错位。


















