启用 obswebsocket.msgpack 子协议需在 websocket 握手时显式声明 sec-websocket-protocol: obswebsocket.msgpack,否则服务端默认使用 json 解析导致二进制帧解析失败;生产中应采用紧凑数组结构、字段索引映射和整数类型优化 msgpack 编码体积,并禁用 permessage-deflate 以避免冗余压缩增加延迟;客户端须正确处理二进制帧,用 uint8array + msgpack.decode 解析并 try/catch 异常,且首帧须验证为 arraybuffer。
直接用 obswebsocket.msgpack 子协议,配合客户端和服务端一致的二进制序列化逻辑,就能把单条消息体积压到 json 的 50% 以内——这不是理论值,是实测中高频事件(如源可见性变更)的典型压缩效果。
如何在连接握手时正确启用 MsgPack
关键不在“能不能用”,而在握手头是否匹配。浏览器或客户端建立 WebSocket 连接时,必须显式带上 Sec-WebSocket-Protocol: obswebsocket.msgpack。如果只传 obswebsocket.json 或漏掉该头,服务端会默认走 JSON 解析路径,后续发二进制帧会被拒绝或解析失败。
- Python 客户端示例(使用
websocket-client):ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE}); ws.connect("wss://localhost:4444", subprotocols=["obswebsocket.msgpack"]) - FastAPI +
websockets服务端需检查request.headers.get("sec-websocket-protocol")是否含obswebsocket.msgpack,再决定调用msgpack.unpackb()而非json.loads() - 常见错误现象:
Invalid frame type或Unexpected token错误,基本都是子协议不匹配导致服务端用 JSON 解析器去读 MsgPack 二进制流
MsgPack 编码必须与数据结构强约定
MsgPack 本身不带 schema,传输 {"scene":"Game", "visible":true} 和 [1, 1] 体积差 5 倍以上。生产环境应放弃“对象直打”,改用字段索引映射:
- 定义紧凑结构:比如
[0, "Game", 1]表示type=0(场景更新)、name="Game"、visible=1;字符串用预设 ID 替代(如"Game"→1) - 避免嵌套 map:MsgPack 对嵌套字典的编码开销远高于 flat array,同语义数据用数组 + 约定顺序更省字节
- 数值类型别乱用:
int比float64少 3–7 字节,布尔值用0/1整数比true/false的 MsgPack tag 更小
为什么不能只换格式,还要关掉 permessage-deflate
很多人启用了 permessage-deflate 扩展,以为“双重压缩更省”,结果延迟反而升高 20ms+。原因很实际:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- MsgPack 本身已高度压缩,再 zlib 压缩收益极低(实测 100 字节内消息压缩率
- 每次收发都要进 zlib 压缩/解压上下文,CPU 开销翻倍,尤其在树莓派或低端云主机上明显卡顿
- obs-websocket 默认未开启该扩展,若服务端强行启用而客户端不支持(如部分小程序 WebView),握手直接失败
- 真正该开压缩的场景是:单条日志 > 500 字节、原始图像 Base64 片段——但这种本就不该走 WebSocket,应走 HTTP 分片上传
客户端接收时如何安全区分 MsgPack 和文本帧
WebSocket 协议允许混合帧类型,但 obswebsocket.msgpack 子协议下,服务端只发二进制帧(Opcode=0x2)。客户端若用 ws.onmessage 监听,会收到 MessageEvent,其 data 是 Blob 或 ArrayBuffer,不是字符串:
- 错误写法:
event.data.toString()—— 会得到乱码,且破坏二进制完整性 - 正确做法:
const uint8 = new Uint8Array(event.data); const obj = msgpack.decode(uint8); - 必须加 try/catch:MsgPack 解码失败会抛
SyntaxError,不捕获会导致整个连接中断 - 调试技巧:用
console.log(Array.from(uint8.slice(0, 16)))快速确认前 16 字节是否符合预期结构(比如首字节是不是你定义的 type ID)
最易被忽略的是“连接建立后第一帧”的处理——有些客户端库(如早期 roslibjs)默认监听 text 类型,收到二进制帧直接丢弃而不报错,现象就是“连上了但没反应”。务必在 onopen 后立刻发一条测试请求,并验证响应帧的 event.type 是 "message" 且 event.data 是 ArrayBuffer 实例。










