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,不走网络,延迟在微秒级。
相关推荐
dingzd9523 分钟前
TikTok账号登录态失效的Cookie字段排查路径
开发语言·php·跨境电商·营销策略
桐盛科技24 分钟前
中央空调全链路管控:基于边缘计算的负荷预判与安全联锁实现
物联网·安全·能源·边缘计算
microrain36 分钟前
把规则链存成文件之后,上线一条规则只需改一个字段
物联网·golang·规则引擎·sagooiot
vipxieliang2 小时前
Laravel 框架内核:请求生命周期、IoC 容器与中间件管道(附 ThinkPHP 选型对比)
php
忆~遂愿2 小时前
极空间部署XiaoMusic:把NAS里的本地音乐接入小爱音箱,搜索、播放和远程控制一起完成
php
ESDWAN2 小时前
SD-WAN 是什么?工作原理、适用场景与选型要点
开发语言·网络·数据库·架构·php
李纲明2 小时前
WordPress 报 Allowed memory size exhausted:先抓堆栈找吃内存的插件,再改 memory_limit
性能优化·php·内存·插件·wordpress·devdiy
sbjdhjd2 小时前
从污点追踪断点到 PHP-FPM 请求队列:HIDS PHP WebShell 样本复盘
开发语言·网络安全·数据挖掘·渗透测试·开源·php·shell
一条泥憨鱼3 小时前
【RabbitMQ】高级篇
java·分布式·rabbitmq·php·ruby