webman不能直接接收mqtt数据,因其无内置broker且http无法解析二进制mqtt协议;正确做法是用process进程通过workerman\mqtt\client订阅外部broker,再异步处理。

Webman 本身不内置 MQTT Broker,也不能直接监听 1883 端口接收设备原始 MQTT 报文;所谓“用 Webman 开发 MQTT 网关”,本质是让 Webman 扮演 MQTT 客户端(订阅者),从外部 MQTT Broker 拉取设备数据,再做解析、存储与分发——它不是网关的接入层,而是后端处理层。
为什么不能在 Webman 的 HTTP 路由里收 MQTT 数据
设备发的是标准 MQTT CONNECT/PUBLISH 流,HTTP 服务根本解析不了。你用 $_POST 或 file_get_contents('php://input') 去接,只会拿到乱码或空内容,因为:
- MQTT 是二进制协议,含固定头、可变头、有效载荷,HTTP 协议栈在 TLS 握手后就直接报错或断连
- Webman 的
onRequest回调是同步执行的,一旦塞入阻塞式 socket 接收逻辑(比如fread()等 MQTT 包),整个事件循环就卡死 - 没有 QoS 保障:HTTP 无重传、无会话保持,设备离线期间消息全丢
正确做法:用独立 Process 订阅 MQTT Broker
Webman 的 support\Process 是最轻量可控的方案,避免多 worker 争抢连接、状态错乱。关键点:
- 必须用
Workerman\Mqtt\Client(非php-mqtt/client),它兼容 Workerman 事件循环,不会阻塞 - 连接失败要重试,但不能无限
sleep(),建议用Timer::add(3, function() { ... })实现指数退避 - 订阅主题建议带通配符,如
/device/+/status,避免为每台设备起一个 client 实例 -
onMessage里别直接写 MySQL —— 改用Redis::lPush()入队,再由另一个定时任务消费,防 DB 成瓶颈
示例片段(精简):
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
class MqttSubscriber extends Process
{
protected function onWorkerStart()
{
$this->client = new \Workerman\Mqtt\Client('mqtt://127.0.0.1:1883');
$this->client->onConnect = function ($client) {
$client->subscribe('/device/+/status');
};
$this->client->onMessage = function ($topic, $content, $client) {
$data = json_decode($content, true);
\Redis::lPush('mqtt:queue', json_encode(['topic' => $topic, 'data' => $data]));
};
$this->client->connect();
}
}
设备上线/掉线状态怎么可靠判断
MQTT 本身有 LWT(Last Will and Testament),但 Webman 消费端无法直接感知 TCP 断连,只能靠 Broker 主动推送遗嘱消息。所以真正可用的状态机制得自己补:
- 设备上报时带上
timestamp字段,Webman 写入 Redis 用SETEX device:A:last_seen 60 <ts></ts> - 另起一个
Timer::add(10, [...])定时扫描所有device:*:last_seen,超 90 秒未更新就触发掉线回调 - 别依赖
onClose—— MQTT client 的连接是长链,onClose只在 client 主动断开时触发,Broker 崩溃或网络闪断不会进来 - 前端展示“在线”状态,必须查 Redis,不能查某个 worker 进程内存里的数组
高频采集下 Redis 写入延迟飙升怎么办
每秒上百台设备同时上报,LPUSH 集中打到单个 Redis 实例,容易触发慢日志甚至连接超时。解决路径很实际:
- 用
Redis::pipeline()批量写,把 10 条消息攒成一次请求,降低网络往返 - 设备 ID 哈希分片:
$shard = crc32($device_id) % 4,写入mqtt:queue:0~mqtt:queue:3,再启 4 个消费进程 - 禁用 Redis 的 AOF(除非你真需要秒级持久化),改用 RDB + 后台
bgsave - 别在
onMessage里调json_encode+strlen+Redis::lPush串行执行,先pack()成二进制或用msgpack_pack()加速序列化
真正容易被忽略的点:MQTT 主题层级设计影响消费性能。如果设备用 /v1/{product_key}/{device_name}/prop 这种深度嵌套主题,subscribe('/v1/+/+/prop') 在 EMQX 上没问题,但在 Mosquitto 2.0 下可能匹配失效——务必在真实 Broker 上验证通配符行为,别只看本地测试。










