Workerman不原生支持Modbus/SNMP/BACnet等工业协议,因其Worker仅基于stream_socket_server支持标准网络协议,而RS485需串口扩展、Modbus TCP需手动组帧解析、SNMP需ASN.1编码且原生函数阻塞,强行嵌入会卡死事件循环、引发粘包断帧、进程失控及资源泄漏;推荐GatewayWorker分层架构:Gateway纯转发WebSocket/HTTP,BusinessWorker处理业务逻辑,独立协议子进程专注协议通信并通过Redis/sysvmsg解耦交互。

Workerman 本身不内置 Modbus、SNMP 或 BACnet 协议栈,做动环监控网关必须自己对接硬件协议,不能只靠 WebSocket 或 HTTP 转发就完事。
为什么不能直接用 Worker 监听串口或 TCP 设备端口?
Workerman 的 Worker 默认基于 stream_socket_server,只支持标准 TCP/UDP/HTTP/WebSocket,不原生支持:
- RS485 串口(如 Modbus RTU)需用
php_serial或ext-serial扩展 + 阻塞读,但会卡死事件循环 - Modbus TCP 虽走 TCP,但协议帧有特定结构(事务ID、协议ID、长度字段),
onMessage不识别,直接收 raw bytes 容易粘包/断帧 - SNMP v2c/v3 查询需构造 ASN.1 编码 PDU,PHP 原生无轻量库支持,
snmpget函数是阻塞的
强行在 onWorkerStart 里开 pcntl_fork 或 proc_open 调外部命令,会导致进程管理失控、信号无法透传、内存泄漏难排查。
推荐架构:GatewayWorker 分层 + 协议适配器进程
用 GatewayWorker 拆成三层,避免协议逻辑污染主事件循环:
-
Gateway进程:纯转发,只管 WebSocket/HTTP 接入,不做解析 —— 绑定websocket://0.0.0.0:8282,负责和前端页面/APP 保持长连接 -
BusinessWorker进程:处理业务逻辑,比如告警规则、数据聚合 —— 但它也不直接连设备 - 独立协议子进程(非 Workerman 管理):用
pcntl_fork启一个或多个常驻子进程,每个专注一种协议(如一个子进程跑 Modbus TCP client,另一个跑 SNMP walk),通过sysvmsg或 Redis pub/sub 与BusinessWorker通信
这样设计后,BusinessWorker 收到设备数据时,只需:Gateway::sendToAllClient(json_encode(['type'=>'temp','value'=>23.5,'ts'=>time()]));,完全解耦。
Modbus TCP 数据采集必须手动组帧和解析
不能依赖 onMessage 自动分包。典型做法是:
- 用
AsyncTcpConnection连接 PLC/RTU 的 502 端口,onConnect后立即发送 Modbus 功能码0x03(读保持寄存器)的完整帧,例如:\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x02 -
onMessage收到 raw data 后,先检查长度 ≥ 9 字节,再校验 MBAP 头(事务ID、协议ID、长度字段),最后提取寄存器值 —— 错误帧(如异常响应0x83)要单独处理 - 定时轮询需用
Timer::add(2000, function() { /* 发送下一帧 */ });,别用sleep(),否则阻塞整个进程
注意:同一设备多个寄存器地址不要合并请求(除非设备支持),多数动环设备对并发 Modbus 请求容忍度极低,容易丢响应。
真实部署时最易忽略的三个点
一是 lanIp 必须设为服务器真实内网 IP(不是 127.0.0.1),否则 Gateway 和 BusinessWorker 之间跨机器通信失败;二是串口类设备采集进程必须加 umask(0) 和 chdir('/');,否则 systemd 重启后权限丢失导致串口打不开;三是所有子进程退出前必须显式 fclose($serial_fd) 或 socket_close($modbus_sock),否则文件描述符泄漏,跑几天后报 Too many open files。

















