workerman 适合作为 iot tcp 双向通信底座,但需手动处理连接生命周期、协议解析(如粘包)、设备身份绑定及跨服务转发;建议在 onconnect 中握手并绑定设备 id,设置心跳,缓存并按协议头切分数据,通过映射表实现 websocket 精准控制硬件。

Workerman 本身不封装硬件通信逻辑,但它是极适合做 IoT TCP 双向通信底座的 PHP 框架——关键在于你得自己处理连接生命周期、协议解析和跨服务转发,不能指望开箱即用。
为什么不能直接用 Worker 类监听硬件端口就完事?
Worker 类监听硬件端口就完事?- 硬件设备(如温湿度传感器、PLC)通常以固定格式发包(比如 Modbus TCP 帧、自定义二进制头+JSON 负载),
Worker的onMessage回调只收到原始字节流,不做解析就无法识别指令或状态 -
onMessage触发时,$connection是单次 TCP 连接对象,但硬件可能长连不发心跳,容易被误判为“空闲断开” - 默认没有连接池、无设备身份绑定机制,多个设备连上来后,
WebSocket端或 HTTP API 想定向推送给某台设备,根本找不到对应$connection
常见错误现象:
- 设备连上后发几条数据就断,日志里没报错 → 实际是
$connection->close()被隐式触发(比如未设置$connection->protocol或未处理粘包) - 同一 IP 多个设备复用连接,后连的把前连的踢掉 →
Worker默认按 IP+port 去重,需手动用设备 ID 替换 key
建议做法:
- 在
onConnect中立刻发送握手响应,并要求设备回传唯一标识(如 MAC、SN) - 把设备 ID 作为键存入全局数组:
$websocket->deviceConnections[$deviceId] = $connection - 设置
$connection->heartbeatIdle = 60和$connection->heartbeatData = 'ping'防止被中间设备断连
onMessage 里怎么安全解析硬件 TCP 数据?
onMessage 里怎么安全解析硬件 TCP 数据?硬件协议几乎都存在粘包、半包问题,不能直接 json_decode($data)。必须先按协议头长度字段切分。
以常见「4 字节长度 + JSON 内容」格式为例:
- 先缓存未完整帧:
$connection->tmpBuffer .= $data - 循环检查是否有完整包:若
strlen($connection->tmpBuffer) >= 4,读前 4 字节得$len = unpack('N', $connection->tmpBuffer)[1] - 若
strlen($connection->tmpBuffer) >= 4 + $len,则截取substr($connection->tmpBuffer, 4, $len)解析,再更新$connection->tmpBuffer
要点:
- 不要用
explode或strpos找分隔符,硬件不会给你发 \n\r - 解析失败时别
close(),记录日志并丢弃当前缓冲区,否则设备重连风暴 - 如果协议含 CRC 校验,必须在切包后立即验证,不通过直接跳过
如何让 WebSocket 页面实时控制某台硬件设备?
不能靠「广播给所有 TCP 连接」,得精准路由。最简方案是:在 WebSocket 服务端维护一个映射表,设备上线时注册,下线时注销。
示例结构:
$websocket->deviceMap = [
'SN-88A2F1' => ['conn' => $tcpConn, 'ip' => '192.168.2.105'],
'SN-B3C9E7' => ['conn' => $tcpConn, 'ip' => '192.168.2.106']
];
当 WebSocket 收到控制指令(如 {"cmd": "relay_on", "device": "SN-88A2F1"}):
- 先查
$websocket->deviceMap[$device]是否存在且$conn->isConnected()为 true - 再调用
$conn->send($payload),注意 payload 必须是硬件能识别的二进制或文本协议格式 - 若设备离线,返回
{"status": "offline", "device": "SN-88A2F1"}给前端,别静默失败
容易踩的坑:
- 忘记判断
$conn->isConnected(),对已断开连接调send()会触发 warning 并卡住事件循环 - WebSocket 消息里设备 ID 写错大小写或带空格,查不到映射 → 建议入库前统一
trim(strtolower()) - 没加超时控制,硬件响应慢导致整个
onMessage阻塞 → 复杂指令应投递到TaskWorker异步处理
要不要用 GatewayWorker 或 think-worker?
如果你的硬件数量超过 50 台,或需要对接 TP 业务系统(比如设备状态存数据库、触发订单流程),直接上 GatewayWorker 更省事。
它天然提供:
-
BusinessWorker专门处理业务逻辑,与Gateway解耦 -
registerAddress自动管理设备连接归属,不用手写映射表 - 内置
Gateway::sendToUid(),只要给设备分配 uid(如SN-88A2F1),就能直接推送
但代价是:
- 多一层进程模型,调试时要分清是
Gateway进程还是BusinessWorker进程出问题 -
onMessage回调里拿不到原始$connection,只能通过Gateway::getClientIdByUid()间接操作,灵活性略降
如果只是小项目(Worker + 手动管理连接更轻量,也更容易定位粘包或协议解析问题。
真正难的从来不是启动几个 Worker,而是设备上线那一刻,你有没有在 3 秒内确认它发的是什么、想干什么、该回什么。











