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,不走网络,延迟在微秒级。
相关推荐
honsor8 分钟前
以太网温湿度传感器:RJ45直连机房的环境监控新方案
运维·网络·数据库·物联网·安全·云计算·github
电子科技圈1 小时前
芯科科技扩展 AI 开发者平台,简化物联网开发并拓展边缘智能
人工智能·科技·物联网
CV工程师丁Sir1 小时前
ArkWeb 手记 06|window.open 与页面跳转拦截
开发语言·php·harmonyos
可乐鸡翅yeah_1 小时前
HLS 业务 Referer‑Policy 网页元标签引发播放异常排错
前端·javascript·网络·ffmpeg·php·音视频
vipxieliang1 小时前
PHP 依赖注入容器从零实现:控制反转、手动注入与容器管理全解析
后端·php
运维行者_1 小时前
PHP性能监控怎么做?从响应时间到慢函数的6个关键指标
运维·服务器·开发语言·网络·支持向量机·php·接口隔离原则
脉动数据行情12 小时前
Go 语言实战|对接美股英伟达 NVDA 实时行情 生产级 WebSocket 方案
开发语言·websocket·golang
Do_It_Today2 小时前
php xml 入门学习资料
开发语言·php
Do_It_Today3 小时前
php url路由入门实例
开发语言·php