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,不走网络,延迟在微秒级。
相关推荐
t13970182 小时前
太阳能板与电池的电压匹配:别用开路电压 Voc,要用 Vmp
嵌入式硬件·物联网·硬件设计·电源管理·太阳能供电
大帅点兵4 小时前
车载 TBOX 大数据采集方案
大数据·物联网
MuMuMu12234 小时前
越华环保集团碳惠小屋|预制舱+物联网碳计量系统设计与工程落地要点
物联网
物联通信量讯说6 小时前
IOTE 2026 释放信号:中国 IoT 出海正在进入“长期运营”阶段
物联网·iot·通信·企业出海
深圳市恒星物联科技有限公司14 小时前
轻量MCU设备通过OpenHarmony兼容性测评的全流程关键要点与实战踩坑经验
大数据·人工智能·物联网·鸿蒙
TDengine (老段)14 小时前
TDengine 常见问题 TOP3
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
4SAPI15 小时前
企业大模型API服务商推荐:从多模型接入到AI API Gateway的技术选型分析
人工智能·php
D_codingXuChu16 小时前
2026年物联网应用开发服务商怎么选:从设备接入到业务闭环的采购评估指南
物联网·技术分享·开发经验
YCOSA202518 小时前
BUG修复---雨晨 Windows 11 IoT 企业版 LTSC 23H2 终结版 22631.7588
windows·物联网
子非鱼a21 小时前
【WEB】[NewStarCTF 2023 公开赛道]include 0。0
php