workerman 不支持原生 mqtt over websocket,因 websocketworker 仅解析 websocket 帧,无法处理原始 mqtt 二进制 connect 包;正确做法是用 asynctcpconnection 模拟 ws 握手后透传 mqtt 流。

Workerman 本身不内置 MQTT 协议解析能力,所谓“多协议网关”本质是多个独立协议服务共存于同一进程模型下,靠端口隔离 + 统一连接管理实现逻辑聚合。直接用 Worker 启一个端口跑 MQTT、另一个跑 WebSocket、再一个跑 TCP 文本协议,是可行且主流的做法;但若想让 MQTT 客户端连上 websocket:// 地址,必须自己封装 WsClient 并透传 MQTT 报文,不能依赖 Workerman 原生 WebSocket 服务器处理 MQTT 流量。
为什么不能直接用 WebSocket 服务器承载 MQTT 流量
Workerman 的 WebsocketWorker 只解析 WebSocket 帧(0x81, 0x82 等),而 MQTT over WS 要求在 WebSocket 连接建立后,客户端发送的是原始 MQTT CONNECT 包(二进制,含 protocol name 0x00 0x04 'M' 'Q' 'T' 'T')。原生 WebsocketWorker 会尝试把 MQTT CONNECT 当作 UTF-8 文本解码,必然失败并断连。
- 现象:
Connection reset by peer或握手后立即onClose - 根本原因:协议栈错位 —— WebSocket 层没透传,MQTT 层收不到原始字节流
- 正确做法:用
AsyncTcpConnection+ 自定义Ws协议类模拟 WS 握手,再把裸 MQTT 流交给workerman/mqtt解析
三个协议如何共存而不冲突
关键不是“融合”,而是“分治”:每个协议独占一个 Worker 实例,共享同一个 registerAddress(如 127.0.0.1:1237),由 GatewayWorker 的注册中心统一纳管连接状态。
- TCP 网关监听
tcp://0.0.0.0:8282,name设为'TCPGateway',pidFile写入runtime/tcp.pid - WebSocket 网关监听
websocket://0.0.0.0:8283,name设为'WSGateway',pidFile写入runtime/ws.pid - MQTT 网关监听
tcp://0.0.0.0:1883(非mqtt://),用workerman/mqtt的Server类启动,pidFile写入runtime/mqtt.pid - 三者
startPort必须错开(如2900/3000/3100),否则 Gateway 内部通信端口会冲突
MQTT 设备上线后怎么跟 WebSocket 管理端互通
不能靠内存变量传递消息 —— Workerman 多进程下 $_SESSION 或全局数组完全不可靠。必须走外部存储做中转:
- 设备上线时,在
onConnect回调里用Redis::set('device:'. $client_id, 'online', ['EX' => 60])标记在线状态 - WebSocket 后台要查设备列表,直接读 Redis key
device:*批量 scan - WebSocket 发送控制指令,写入 Redis Pub/Sub 频道
control:device_xxx,单独起一个Worker订阅该频道,再调用$mqtt_connection->publish()推送 - 千万别在
onPublish里同步查 MySQL 或调 HTTP 接口 —— QoS1 场景下延迟飙升会导致设备反复重发
最容易被忽略的兼容性雷区
PHP 版本和组件版本的隐式绑定比想象中更紧:
-
think-worker ^3.0强依赖Workerman ^3.5,而workerman/mqtt ^1.0在 PHP 8.2+ 下需手动 patchpack('H*', ...)调用,否则 CONNECT 包解析失败 -
gatewayclient必须与Workerman主版本一致,^3.0对应Workerman 3.x,混用4.x会导致registerAddress连接超时 - MQTT 5.0 的
SessionExpiryInterval字段在workerman/mqtt中未完整实现,如果设备带该字段上线,服务端会静默丢弃 CONNECT 包 —— 必须在onConnect前加if (substr($raw_data, 0, 2) === "\x10\xXX") { ... }手动跳过可变头扩展字段











