webman 必须通过 workerman/mqtt 扩展在独立 process 中运行 mqtt 客户端,禁止在控制器中直接使用阻塞式 mqtt 库;正确做法是创建 mqttprocess,在 onstart 初始化 client 并在 onconnect 中订阅,消息处理应轻量且避免阻塞主循环。

Webman 本身不支持 MQTT,必须用 workerman/mqtt 扩展跑在独立 Process 中
Webman 没有内置 MQTT 客户端能力,直接在控制器里 new php-mqtt/client 或调用 mosquitto-php 会导致连接卡死、内存泄漏、消息丢失。根本原因是:Webman 基于 Workerman 的事件循环,而 php-mqtt/client 是阻塞 I/O,会拖垮整个 Worker 进程;它还要手动调用 loop(),跟 Workerman 主循环冲突;也没有自动重连,Broker 重启后连接就永久失效。
正确做法只有一条:把 MQTT 客户端放进 app/process/MqttProcess.php,继承 support\Process,在 onStart 里初始化 \Workerman\Mqtt\Client 实例,并设置 reconnect_period(建议 5–10 秒)。
- 订阅逻辑必须写在
onConnect回调里,不能提前调用subscribe() -
onMessage里只做轻量转发,比如redis()->lPush('mqtt_incoming', ...),别直接查库或发 HTTP 请求 - Client ID 要带唯一标识,例如
'webman-' . uniqid(),避免 Broker 拒绝重复连接
设备上线/离线状态怎么实时同步到 Web 管理后台
MQTT 的 $SYS/broker/clients/+/connected 或自定义的 device/+/online 主题可捕获设备上下线,但 Webman 的 HTTP 接口无法主动推送给前端。常见错误是轮询数据库或 Redis,延迟高、压力大。
推荐组合方案:workerman/mqtt 收到上线消息 → 写入 Redis(如 SET device:123:status "online")→ 后台 WebSocket 连接通过 GatewayWorker 或 Webman 自带的 WebSocket 进程监听 Redis Pub/Sub 或定时拉取状态 → 前端用 WebSocket 接收变更。
- 不要在 MQTT Process 里直接调用
Worker::sendToWorker()推送状态给 HTTP Worker,容易丢消息或阻塞 - Redis 的
EXPIRE必须设(比如 60 秒),防止离线设备状态残留 - 前端页面首次加载时,应先 GET 一次设备列表状态,再建立 WebSocket 连接,避免初始态缺失
命令下发为什么不能直接 publish 到 MQTT,而要走队列
用户在后台点击“重启设备”,如果后端立刻调用 $mqtt->publish('device/123/cmd', 'reboot'),会出现三种典型问题:MQTT 连接可能还没建好;Broker 可能暂时不可达;并发下发时容易触发 QoS 0 丢包或 QoS 1 重复投递。
可靠做法是把指令存进 Redis List 或 Beanstalkd 队列,由一个专用的 CommandProcess 消费并重试发布。这个 Process 和 MqttProcess 是平级关系,都运行在 app/process/ 下,共享同一个 MQTT Client 实例(需用 static 属性或全局变量管理单例)。
- publish 失败时,
sleep(1)后重试,最多 3 次,超时后写入失败日志表 - 指令体必须包含
timestamp和seq,方便设备端去重和幂等处理 - 不要在 HTTP 请求中等待 publish 返回结果,应立即返回“已入队”,前端轮询执行状态
动态订阅主题(比如新增设备)必须重启 Process?
是的,app/process/MqttProcess.php 默认只启动一次,onConnect 里的 subscribe() 只执行一遍。设备列表变化后,旧订阅不会自动更新,新设备主题收不到消息,硬编码 subscribe('device/+/status') 也解决不了精确匹配需求(比如只订 device/{id}/status)。
可行解法是让 MqttProcess 监听 Redis 的 device:sub:update 事件,收到后调用 $mqtt->unsubscribe() + $mqtt->subscribe() 切换主题。但要注意:unsubscribe 不是原子操作,中间可能漏消息;且 workerman/mqtt 的 subscribe 是异步回调,必须等 onSubscribed 触发后再认为生效。
- 每次重新订阅前,先清空旧的
onMessage回调绑定,避免闭包堆积内存 - 主题列表应缓存在 Redis 中(如
HGETALL mqtt:subscriptions),Process 启动时读一次,后续只响应变更事件 - 生产环境务必加锁,防止多个实例同时 reload 订阅导致 Broker 连接数超标
reconnect_period 和异常日志捕获,往往决定系统能否扛住网络抖动。这些细节不在框架文档里,但错一处就整片掉线。











