workerman收到的$data是原始二进制字节流,非自动识别十六进制;判断硬件发送的是hex文本还是原始二进制,需通过抓包工具或bin2hex($data)输出分析:含空格/大写字母/非hex字符为文本格式,纯小写a–f+0–9且长度为偶数则多为原始二进制;hex文本需先正则清洗再hex2bin()转二进制,原始二进制可直接用于帧解析与crc计算。

Workerman 不会自动识别或转换十六进制字符串,你收到的 $data 是原始二进制字节流;所谓“十六进制数据”其实是硬件端以文本形式(如 "A5 FC 0B")发送的 ASCII 字符串,还是直接发送的二进制字节(如 \xA5\xFC\x0B),必须先分清——这是后续所有处理的前提。
怎么判断硬件发来的是 Hex 文本还是原始二进制?
关键看串口助手或抓包工具显示内容:
- 如果看到
"A5 FC 0B"、"00 1B 30 30"这类带空格/可读字符的字符串 → 硬件发的是 Hex 文本,即每个字节被编码成两个 ASCII 字符('A'是 0x41,不是 0xA5) - 如果看到乱码、不可见字符或
bin2hex($data)输出为连续无空格的十六进制(如"a5fc0b")→ 硬件发的是 原始二进制,$data就是你要的真实帧 - 用
bin2hex($data)打印出来一眼可辨:有空格/大写字母/含非 hex 字符(如'G')→ 文本格式;纯小写 a–f + 0–9 且长度为偶数 → 很可能是原始二进制(需结合协议确认)
收到 Hex 文本时,如何转成可用的二进制帧?
不能直接 hex2bin($data),因为中间有空格、换行或大小写混杂。必须先清洗再转换:
- 用
preg_replace('#[^0-9a-fA-F]#', '', $data)去掉所有非十六进制字符(比只删空格更鲁棒) - 确保清洗后长度为偶数,否则
hex2bin()会返回false;可补'0'或丢弃残帧 - 调用
hex2bin($cleaned)得到真实二进制帧,之后才能解析帧头、长度、CRC 等字段 - 示例:
$data = "A5 FC 0B 00"; $cleaned = preg_replace('#[^0-9a-fA-F]#', '', $data); $binary = hex2bin($cleaned); // → \xA5\xFC\x0B\x00
收到原始二进制时,为什么还要用 bin2hex() 调试?
因为 PHP 日志和 var_dump 默认不显示二进制内容,直接打印会乱码甚至截断。调试阶段必须人工可读:
- 在
onMessage开头加var_dump(bin2hex($data));,确认收到的字节是否符合预期(比如帧头是不是"a5fc") - 不要在生产环境长期开启,
bin2hex()对大包有明显性能开销 - 切帧逻辑(如找
\xAA\xBB、读长度字段)必须基于原始$data,不是bin2hex($data)的结果 - CRC16 计算也必须用原始二进制字节,
hex2bin()后的数据才能喂给crc16()函数
为什么不能在 onMessage 里直接处理所有逻辑?
因为 TCP 流式特性导致单次 $data 几乎从不等于一帧完整报文——它可能只是半帧、两帧粘连、或带残留校验位。真正可靠的处理必须:
- 为每个
$connection维护独立缓冲区:$connection->recv_buffer ??= ''; - 每次追加:
$connection->recv_buffer .= $data; - 循环切帧:按协议规则(如帧头+长度字段)从缓冲区提取完整帧,成功则
substr掉已处理部分,失败则等待下次onMessage - 设超时清理:避免设备异常断连后缓冲区无限增长,可用
$connection->last_recv_time = time();配合定时器检查
最易被忽略的一点:帧解析代码必须能处理「跨两次 onMessage 到达的同一帧」,也就是缓冲区中始终可能存着上一次没切完的尾巴——这个状态管理不在框架里,全靠你自己守住连接级上下文。











