Workerman WebSocket服务端默认以文本帧发送数据,若直接send二进制内容而未设置$connection->websocketType为BINARY_TYPE_ARRAYBUFFER,会导致浏览器因帧类型与内容不匹配而断连;必须每次发送前显式设置该属性,且浏览器端需同步设ws.binaryType='arraybuffer'。

Workerman 的 WebSocket 服务端默认只认 UTF-8 文本,直接 send() 二进制数据(比如 pack() 出来的字节、file_get_contents() 读的图片)会触发协议错误,浏览器断连——这不是你代码写错了,是协议层校验失败。
为什么 send() 二进制数据会断连
WebSocket 协议要求:发送帧必须在帧头中标明类型(text 或 binary)。Workerman 默认把所有 $connection->send($data) 当作文本帧发出去,哪怕 $data 是 raw bytes。浏览器收到二进制内容却看到 text 标记,直接关闭连接并报错 Invalid frame header 或 Connection closed before receiving a handshake response。
关键点:$connection->websocketType 必须显式设置,且只能在 onMessage 或业务逻辑中设置,不能在 onConnect 里提前设(此时连接尚未完成 WebSocket 握手)。
-
$connection->websocketType = \Workerman\Protocols\Websocket::BINARY_TYPE_ARRAYBUFFER:告诉 Workerman 这次 send 要走 binary 帧 -
$connection->websocketType = \Workerman\Protocols\Websocket::BINARY_TYPE_BLOB:对应浏览器端binaryType = 'blob',但极少用 - 不设或设错,就等同于强制发 text 帧,二进制数据必炸
怎么正确发二进制数据给浏览器
分两步:先改类型标记,再发原始字节。顺序不能反,且每次发二进制前都得设(它不是连接级配置,而是单次 send 的上下文)。
示例:推送一张 PNG 图片的原始字节
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
// 假设 $png_data = file_get_contents('avatar.png');
$connection->websocketType = \Workerman\Protocols\Websocket::BINARY_TYPE_ARRAYBUFFER;
$connection->send($png_data);
- 必须用原始字节字符串(
string类型,不是 base64、不是 JSON),Workerman 会原样塞进 binary 帧 payload - 不能在
onConnect里设websocketType—— 此时连接还没升成 WebSocket,设了也无效 - 如果连续发多次二进制,每次
send()前都要重新设websocketType,它不会保持状态 - 发完二进制后若还要发 JSON 文本,记得切回默认(或不设),否则下一次
send()还是 binary 帧
浏览器端接收时 binaryType 必须匹配
服务端发的是 ARRAYBUFFER 类型帧,浏览器端必须设 ws.binaryType = 'arraybuffer',否则 event.data 是 Blob,要额外调 blob.arrayBuffer(),变成异步操作,破坏实时性。
正确写法(放在 onopen 里):
const ws = new WebSocket('ws://your-server.com');
ws.onopen = () => {
ws.binaryType = 'arraybuffer'; // 关键!必须在这之后收消息
};
ws.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
const uint8 = new Uint8Array(e.data);
console.log('收到', uint8.length, '字节');
}
};
- 设
binaryType必须在onopen之后、任何onmessage触发之前;设晚了,第一批消息可能已按默认 blob 方式交付 - 不要设成
'blob'再手动转 —— 对大文件来说,blob.arrayBuffer()是 Promise,无法保证顺序和低延迟 - 如果服务端混用 text 和 binary 消息,浏览器端靠
typeof event.data或instanceof判断类型即可,无需额外协议头
常见踩坑点:大文件分片与内存
Workerman 不自动分片。传几十 MB 的文件时,$connection->send($big_binary) 会把整个字符串加载进内存,可能触发 OOM 或阻塞事件循环。
- 务必自己分片:用
str_split($big_binary, 65536)切成 64KB 块,逐块 send,并在每块前加长度头或序号 - 每发一块后,不要立刻发下一块 —— 等
onBufferDrain或加简单延时,避免客户端缓冲区溢出 - 服务端不要长期 hold 住大
string变量,发完即unset(),PHP GC 才能及时回收 - 浏览器端接收时,用
Uint8Array.prototype.set()合并分片,别用concat()创建新数组,避免重复拷贝
最易被忽略的是:binaryType 设置时机和服务端 websocketType 设置的绑定关系——它们是一对硬性匹配项,缺一不可,且都必须在“发送动作发生前一刻”生效。漏掉任意一个,连接就断,没别的原因。

















