workerman 默认不处理tcp粘包/断包,onmessage收到的是原始字节流而非完整消息;需自行实现连接级缓冲+帧解析逻辑,按硬件协议(帧头、长度字段、校验和等)切帧,并设超时清理防止内存泄漏。

Workerman 默认不处理 TCP 流式粘包/断包,onMessage 回调收到的 $data 是任意长度的原始字节流,不是“一条完整消息”。硬件设备(如串口转TCP模块、PLC、传感器网关)通常按固定帧结构发包,而 Workerman 不会自动识别帧头、长度字段或校验位,直接把底层 socket recv() 的结果扔进 onMessage —— 这就是断包和少字节的根本原因。
为什么 onMessage 拿到的数据总是不完整?
TCP 是面向字节流的协议,没有天然消息边界。硬件端可能一次发 512 字节完整帧,但内核 TCP 栈可能分两次通知 PHP:第一次 200 字节,第二次 312 字节;也可能把两个帧合并成一次通知(粘包)。Workerman 的 onMessage 只负责转发这两次通知,不做任何缓冲或重组。
- 你看到的“少字节”,大概率是帧还没收全就被回调触发了
- “断包”则是上一帧的尾部和下一帧的头部混在同一个
$data里 -
Worker::$protocol = 'text'完全无效——它只认\n或\r\n,硬件协议几乎不用这个
怎么让 Workerman 正确解析硬件定义的帧格式?
必须自己实现缓冲 + 帧解析逻辑,不能依赖框架内置机制。典型做法是在 onMessage 中维护连接级缓存,并按硬件协议规则切帧:
- 用
$connection->frame_buffer ??= ''存未处理完的残余数据 - 把新来的
$data追加到缓冲区:$connection->frame_buffer .= $data - 按协议提取完整帧:比如帧头是
\xAA\xBB,长度字段在第 4–5 字节(大端),就循环扫描缓冲区找\xAA\xBB,读出长度,判断当前缓冲区是否够长 - 够长则截取并处理,剩余部分留在
frame_buffer;不够长就等下次onMessage - 务必设超时清理缓冲区,避免内存泄漏(例如 30 秒没收到完整帧就清空)
哪些硬件协议特征会让解析更难?
不同设备协议差异大,直接影响缓冲逻辑复杂度:
- 变长帧(含长度字段)比固定长度帧难处理,要小心字节序(
unpack('n', $buf)vsunpack('v', $buf))和字段偏移 - 带校验和的帧,必须在切帧后验证,失败要丢弃整帧,不能残留错误数据污染缓冲区
- 帧头不唯一(如只用
0x02),容易误触发,建议配合帧尾或长度字段双重判断 - 某些工业设备会在帧间插入空闲时间(idle time),但 Workerman 无空闲检测机制,只能靠超时+长度推断
- 别假设
$data是 UTF-8 —— 硬件二进制数据用mb_strlen($data, '8bit')或strlen($data)计算字节数
真正麻烦的不是写几行切帧代码,而是协议文档模糊、设备固件有 bug、或者同一型号不同批次帧格式微调。上线前一定用 Wireshark 抓包确认实际收发内容,再写解析逻辑——光看文档十有八九会翻车。











