PHP Swoole实现物联网MQTT数据订阅与WebSocket实时推送

性能实测

我们在实际项目里跑了这套方案,设备数约200台,每台每5秒上报一次温湿度数据。实测数据:

指标 数值
MQTT消息处理延迟 小于5ms
WebSocket推送延迟 小于50ms
内存占用 约80MB
CPU占用 单核10%以下

200台设备每秒约40条消息,Swoole处理起来很轻松。瓶颈不在PHP,而在EMQX的消息路由和前端图表渲染。

虎王科技在物联网平台开发中积累了大量Swoole实战经验,更多技术文章和开源项目可以访问我们的GitHub仓库(github.com/huwangkeji)和技术博客(heicat.com)。团队长期承接物联网平台后端开发、嵌入式固件定制和边缘计算方案设计,欢迎交流合作。

PHP Swoole实现物联网MQTT数据订阅与WebSocket实时推送

物联网项目中,设备端走MQTT协议上报数据到Broker,后端需要把数据实时推送到Web大屏。传统方案是前端轮询接口,延迟高、服务器压力大。本文分享我们在沧州虎王科技物联网平台中,用PHP Swoole搭建的MQTT订阅转WebSocket推送方案,一套PHP进程同时处理协议转换和实时推送。

技术选型:为什么是Swoole而不是Node.js

物联网平台后端语言选型经常在PHP和Node.js之间犹豫。我们团队之前用Node.js写过类似的推送中间件,后来切到Swoole,原因有三点:

对比项 Node.js + ws PHP + Swoole
长连接性能 基于libuv事件循环 基于Reactor线程+Worker进程
与已有业务整合 需要独立服务 可复用PHP框架和ORM
开发者成本 前端转后端有门槛 PHP开发者直接上手
进程管理 需pm2守护 Swoole自带进程管理

Swoole的Master-Worker进程模型天然适合这种场景:Reactor线程负责网络IO,Task Worker跑MQTT订阅,普通Worker处理WebSocket连接,各进程之间通过管道通信,互不阻塞。

架构设计

整体数据流向:

复制代码
设备 -> MQTT Broker(EMQX) -> Swoole Task Worker(订阅) -> Table共享内存 -> WebSocket Worker(推送) -> 前端大屏

核心思路是把MQTT订阅和WebSocket推送放在同一个Swoole服务里。用Swoole Table做进程间数据共享,避免引入Redis增加复杂度。MQTT订阅放在Task Worker进程,收到数据后写入Table,WebSocket Worker定时读Table推给前端。

MQTT订阅端实现

先装依赖包,用bluerhinos/phpMQTT作为MQTT客户端:

bash 复制代码
composer require bluerhinos/php-mqtt
pecl install swoole

MQTT订阅跑在一个独立的Task进程里,连接到EMQX Broker订阅设备数据Topic:

php 复制代码
<?php
use Swoole\Table;
use Swoole\WebSocket\Server;

$ws = new Server("0.0.0.0", 9502);
$ws->set([
    'worker_num' => 2,
        'task_worker_num' => 1,
            'heartbeat_idle_time' => 60,
                'heartbeat_check_interval' => 30,
                ]);
// 创建共享内存表,存最新设备数据
$deviceTable = new Table(1024);
$deviceTable->column('data', Table::TYPE_STRING, 4096);
$deviceTable->column('timestamp', Table::TYPE_INT, 8);
$deviceTable->create();

$ws->on("WorkerStart", function ($server, $workerId) use ($deviceTable) {
    if ($workerId === 0) {
            $mqtt = new Bluerhinos\PhpMqtt\Client(
                        "192.168.1.100", 1883, "php-subscriber"
                                );
                                        $mqtt->connect();
                                                $mqtt->subscribe("device/+/telemetry", function ($topic, $msg) use ($deviceTable) {
                                                            preg_match('/device\/(.+)\/telemetry/', $topic, $m);
                                                                        $deviceId = $m[1] ?? 'unknown';
                                                                                    $payload = json_decode($msg, true);
                                                                                                $payload['device_id'] = $deviceId;
                                                                                                            $payload['timestamp'] = time();
                                                                                                                        $deviceTable->set($deviceId, [
                                                                                                                                        'data' => json_encode($payload),
                                                                                                                                                        'timestamp' => time(),
                                                                                                                                                                    ]);
                                                                                                                                                                            });
                                                                                                                                                                                    while (true) {
                                                                                                                                                                                                $mqtt->loop(1);
                                                                                                                                                                                                        }
                                                                                                                                                                                                            }
                                                                                                                                                                                                            });
                                                                                                                                                                                                            ```
这段代码的关键点是`WorkerStart`回调里判断`$workerId`。Task Worker的ID从0开始,所以第一个Task进程专门跑MQTT订阅。MQTT消息进来后直接写Swoole Table,不走网络,延迟在微秒级。
相关推荐
物联网IoT小易14 分钟前
Modbus RTU设备如何接入物联网平台?从串口采集到MQTT上云
物联网·modbus·物联网平台·modbus协议·modbus rtu·modbus rtu设备接入
2601_962381581 小时前
物联网场景下的 Python 边缘计算轻量化方案解析与实战落地
python·物联网·边缘计算·数据处理·轻量化方案
逗脑IDE1 小时前
零基础学ESP32:蜂鸣器——让ESP32会“唱歌”!
python·物联网·esp32·智能家居
电子科技圈11 小时前
从工厂直达全球市场:emnify为联网设备制造商推出免费SGP.32引导网络接入的OEM计划
嵌入式硬件·mcu·物联网·智能硬件·iot·交通物流
智码看视界12 小时前
Edge AI 全栈1:ESP32 传感器采集到云端大模型推理的完整链路
人工智能·物联网·嵌入式·esp32·aiot·全栈开发·edgeai
XR12345678813 小时前
办公网自主创新建设怎么选?信创办公场景的选型逻辑
开发语言·php
Hotchip_MEMS15 小时前
从“手工作坊”到“全自动贴装”:MEMS如何重塑雾化器制造流程
人工智能·笔记·物联网·电脑·制造
CRMEB15 小时前
不懂设计也能有精美商城?CRMEB 主题广场与可视化装修的“千店千面“实操
人工智能·ai·小程序·开源·php
广凌股份(广凌科技)15 小时前
广凌智慧教室融合管理平台|现代技术如何改善课堂体验?
运维·人工智能·物联网