Swoole 4 WebSocket 发送二进制数据失败的核心原因是未按 RFC 6455 正确设置帧类型(Opcode=2)和掩码位,导致客户端拒绝解析;应显式使用 WEBSOCKET_OPCODE_BINARY 或手动构造 0x82 开头的二进制帧,并确保客户端设置 binaryType='arraybuffer'。

这个问题核心不在“编码错误”,而在于 Swoole 4 的 WebSocket 服务默认发送二进制帧时,未按 RFC 6455 规范正确设置掩码(Mask)位,导致客户端(尤其是浏览器)拒绝解析,报类似 Invalid frame header 或静默丢弃数据。
为什么 Swoole 4 发二进制容易出问题?
Swoole 的 $server->push() 或 $fd->send() 方法在传入 string 类型且内容为二进制时,底层默认不启用掩码 —— 但 RFC 明确规定:客户端发帧必须 Mask,服务端发帧可不 Mask;但浏览器作为客户端,只接受服务端发来的、Mask 位为 0 的帧(即不掩码);而某些旧版或严格实现的客户端库(如部分 JS WebSocket polyfill、小程序基础库)会误判无 Mask 的服务端帧为非法。
更常见的是:你用 pack() 或 hex2bin() 构造了原始字节,直接 send($rawBytes),Swoole 没识别出这是“需按 Binary 帧封装的数据”,而是当成普通文本帧(Opcode=1)发出,首字节错为 0x81 而非 0x82,客户端 decoder 直接报帧头无效。
确认是否真为帧类型/掩码问题
- 打开 Chrome DevTools → Network → WS → Frames 标签页,看“Type”列:正常二进制帧应显示 Binary,若显示 Text 或为空/乱码,说明服务端没发对帧类型
- 用 Wireshark 抓包,过滤
websocket,检查帧首字节:二进制帧 FIN+Opcode 应为0x82(FIN=1, Opcode=2),若看到0x81(文本)或0x02(FIN=0),即结构错误 - 服务端日志加一行:
var_dump(bin2hex(substr($data, 0, 2))); // 看前两字节
正确发送二进制数据的两种方式
方式一:显式构造 WebSocket Binary 帧(推荐,完全可控)
不用依赖 Swoole 自动识别,自己按规范组帧:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 首字节 =
0x82(FIN=1, Opcode=2) - 次字节 =
0x80 | $len(若长度<126)或0xFE + $len(2字节)或0xFF + $len(8字节),并设 Mask=0(服务端发给浏览器) - 后接原始 payload(无需 mask)
- 然后
$server->push($fd, $frame, WEBSOCKET_OPCODE_BINARY);(Swoole 4.5+ 支持该 flag)
方式二:确保 Swoole 正确识别并封装(简洁但有版本依赖)
必须满足三个条件:
- 使用
WEBSOCKET_OPCODE_BINARY显式指定类型:$server->push($fd, $binaryData, SWOOLE_WEBSOCKET_OPCODE_BINARY); - 传入数据必须是
string类型(不能是 array/object),且内容为原始二进制字节(不是 JSON 字符串) - Swoole 版本 ≥ 4.4.16(修复了早期 binary 帧 opcode 错置 bug);建议升级到 4.8.x 或更高
客户端接收端也要匹配
JS 端必须监听 binaryType 并设为 "arraybuffer",否则收到二进制会被自动转成字符串,再 new Uint8Array() 就错乱:
const ws = new WebSocket('ws://...');
ws.binaryType = 'arraybuffer'; // 关键!
ws.onmessage = e => {
if (e.data instanceof ArrayBuffer) {
const view = new Uint8Array(e.data);
console.log('Received binary:', view);
}
};
不复杂但容易忽略:Swoole 发二进制,关键在帧类型明确、掩码位合规、客户端类型匹配。绕过自动推断,手动控帧最稳。

















