如何在接收MQTT消息时响应Ajax请求(PHP环境)
解决方案:分离MQTT监听与HTTP请求处理
因为MQTT客户端的loop(true)是阻塞式循环,会持续占用进程无法处理其他逻辑,所以不能把它和HTTP请求处理放在同一个脚本里。正确的做法是把MQTT监听做成独立的后台常驻进程,将收到的消息存入共享存储(比如Redis、数据库),而处理Ajax请求的PHP脚本只负责从存储中读取最新数据。
步骤1:编写独立的MQTT监听后台脚本
修改你的MQTT代码,将收到的消息存入Redis(性能最优,适合实时场景):
<?php require('vendor/autoload.php'); use PhpMqtt\Client\MqttClient; use PhpMqtt\Client\ConnectionSettings; // 初始化Redis连接(确保已安装Redis扩展) $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 如果Redis有密码,添加$redis->auth('your_password'); $server = '<address>'; $port = 1883; $clientId = 'mqtt_listener_' . uniqid(); // 避免客户端ID重复 $username = '<user>'; $password = '<password>'; $mqtt = new MqttClient($server, $port, $clientId); $connectionSettings = (new ConnectionSettings) ->setUsername($username) ->setPassword($password) ->setKeepAliveInterval(60) ->setLastWillQualityOfService(1); $mqtt->connect($connectionSettings, true); $mqtt->subscribe('wsc/power', function ($topic, $message) use ($redis) { // 将消息存入Redis,用topic作为键覆盖旧值(保留最新数据) $redis->set('mqtt:' . $topic, $message); // 如果需要保存历史消息,可用列表存储并限制长度 // $redis->lpush('mqtt:' . $topic . ':history', $message); // $redis->ltrim('mqtt:' . $topic . ':history', 0, 99); // 只保留最新100条 }, 0); // 启动阻塞循环监听MQTT消息 $mqtt->loop(true);
将这个脚本保存为mqtt_listener.php,然后后台启动:
# 用nohup让脚本后台运行,输出重定向到空设备 nohup php mqtt_listener.php > /dev/null 2>&1 &
更可靠的方式是用systemd制作服务,确保进程意外退出后自动重启。
步骤2:修改Ajax请求处理脚本,读取共享存储中的MQTT消息
在原有的HTTP处理脚本中,添加读取Redis数据的逻辑,响应前端的数据请求:
<?php $result = []; $result['success'] = false; $result['message'] = "unknown"; // 初始化Redis连接 $redis = new Redis(); $redisConnectSuccess = $redis->connect('127.0.0.1', 6379); // 处理Redis连接失败的情况 if (!$redisConnectSuccess) { $result['message'] = "数据存储服务连接失败"; echo json_encode($result); exit; } if (isset($_POST['action'])) { switch ($_POST['action']) { case 'quit': $msg = shell_exec('sudo systemctl stop startup.service'); $result['success'] = true; $result['message'] = $msg; break; case 'restart': $msg = shell_exec('sudo systemctl restart startup.service'); $result['success'] = true; $result['message'] = $msg; break; case "get-power-data": // 从Redis读取最新的MQTT消息 $powerData = $redis->get('mqtt:wsc/power'); if ($powerData !== false) { $result['success'] = true; $result['data'] = $powerData; $result['message'] = "获取数据成功"; } else { $result['message'] = "暂无数据"; } break; case "do-something": // 执行自定义逻辑 $result['success'] = true; // 根据实际逻辑修改为true/false $result['message'] = "操作完成"; break; } echo json_encode($result); }
步骤3:前端通过Ajax轮询获取最新数据
前端定时发送Ajax请求调用get-power-data动作,即可获取MQTT推送的最新数据:
// 每3秒轮询一次 setInterval(() => { fetch('/your-php-script.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'action=get-power-data' }) .then(response => response.json()) .then(data => { if (data.success) { // 更新网页内容 document.getElementById('power-data').textContent = data.data; } }) .catch(error => console.error('请求失败:', error)); }, 3000);
关键注意事项
- 确保Redis服务正常运行,PHP已安装Redis扩展(可通过
php -m | grep redis检查)。 - MQTT监听进程需要长期运行,建议用systemd或supervisor管理,避免意外退出。
- 如果使用数据库替代Redis,注意处理并发写入的问题,确保数据一致性。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

