MQTT 协议完全入门指南
MQTT 是物联网(IoT)领域最重要的通信协议之一,比 Modbus 和 CAN 更现代化,非常适合你的项目。我们从零开始。
一、MQTT 基础概念
1.1 什么是 MQTT?
MQTT = Message Queuing Telemetry Transport(消息队列遥测传输)
传统的点对点通信: 设备A ←→ 设备B 设备A ←→ 设备C 设备B ←→ 设备C (需要知道对方的IP地址) MQTT 发布/订阅模式: MQTT Broker(中间人) ↑ ↓ ┌────────────┼────────────┐ ↓ ↓ ↓ 设备A 设备B 设备C (发布者) (订阅者) (订阅者) 设备A 发布 "温度25°C" → Broker → 自动分发给订阅了"温度"主题的设备B和C
核心特点:
-
✅ 发布/订阅模式:设备不需要知道对方的存在
-
✅ 轻量级:协议开销极小,适合嵌入式设备
-
✅ 低带宽:非常适合网络不稳定的环境
-
✅ QoS 保证:三级服务质量保证消息可靠性
1.2 核心角色
1. Broker(代理/服务器) - 接收所有消息 - 根据主题分发消息 - 管理客户端连接 - 常用软件:Mosquitto, EMQX, HiveMQ 2. Publisher(发布者) - 发送消息到 Broker - 指定消息的主题(Topic) 3. Subscriber(订阅者) - 订阅感兴趣的主题 - 接收 Broker 分发的消息 4. Topic(主题) - 消息的"地址" - 类似文件路径:home/room1/temperature
1.3 工作流程
步骤1: 订阅者连接到 Broker 订阅者A → CONNECT → Broker Broker → CONNACK → 订阅者A 步骤2: 订阅者订阅主题 订阅者A → SUBSCRIBE "sensor/temperature" → Broker Broker → SUBACK → 订阅者A 步骤3: 发布者连接并发布消息 发布者X → CONNECT → Broker 发布者X → PUBLISH "sensor/temperature" "25°C" → Broker 步骤4: Broker 分发消息 Broker → PUBLISH "sensor/temperature" "25°C" → 订阅者A
二、主题(Topic)详解
2.1 主题命名规则
主题是分层的,用 / 分隔: home/livingroom/temperature home/livingroom/humidity home/bedroom/temperature factory/workshop1/machine5/status 规则: - 区分大小写 - 不能以 / 开头或结尾 - 不能包含空格 - 长度限制:65535 字节 - 建议使用小写字母
2.2 通配符订阅
+ (单层通配符) 订阅: home/+/temperature 匹配: ✓ home/livingroom/temperature ✓ home/bedroom/temperature ✗ home/livingroom/sensor/temperature (超过一层) # (多层通配符,必须在最后) 订阅: home/# 匹配: ✓ home/livingroom/temperature ✓ home/bedroom/humidity ✓ home/livingroom/sensor/CO2 ✓ home (自己) 订阅: home/livingroom/# 匹配: ✓ home/livingroom/temperature ✓ home/livingroom/sensor/CO2 ✗ home/bedroom/temperature
2.3 系统主题
$开头的主题是系统保留的: $SYS/broker/version # Broker 版本 $SYS/broker/uptime # 运行时间 $SYS/broker/clients/total # 客户端总数 $SYS/broker/messages/sent # 发送的消息数 注意:客户端不能发布到 $SYS 主题
三、QoS(服务质量)等级
3.1 三个等级对比
| QoS | 名称 | 保证 | 性能 | 应用场景 |
|---|---|---|---|---|
| 0 | At most once | 最多一次,可能丢失 | 最快 | 不重要的数据(温度每秒都有) |
| 1 | At least once | 至少一次,可能重复 | 中等 | 重要但可容忍重复(报警) |
| 2 | Exactly once | 恰好一次 | 最慢 | 关键数据(交易、控制指令) |
3.2 QoS 0 流程
发布者 → PUBLISH(QoS=0, "温度25°C") → Broker ↓ 订阅者收到 特点:发完即忘,不管对方有没有收到
3.3 QoS 1 流程
发布者 → PUBLISH(QoS=1, ID=123, "温度25°C") → Broker ←────────── PUBACK(ID=123) ────────── ↓ 订阅者收到 特点:发送方等待确认(ACK),如果超时会重发
3.4 QoS 2 流程(四次握手)
发布者 → PUBLISH(QoS=2, ID=123) → Broker
←────── PUBREC(ID=123) ──────
───────→ PUBREL(ID=123) ──────→
←────── PUBCOMP(ID=123) ──────
↓
订阅者收到
特点:最可靠,保证不丢失、不重复,但最慢
四、保留消息和遗嘱消息
4.1 保留消息(Retained Message)
用途:新订阅者立即获得最新状态
发布者 → PUBLISH(Topic="room/light", Payload="ON", Retain=true) → Broker
↓
Broker 保存这条消息
新订阅者连接 → SUBSCRIBE("room/light") → Broker
←─ 立即收到 "ON" ─────────
应用场景:
- 设备状态(灯开/关)
- 配置信息
- 最新传感器读数
4.2 遗嘱消息(Last Will and Testament, LWT)
用途:客户端异常断线时,Broker 自动发布的消息 客户端连接时设置遗嘱: CONNECT( will_topic="device/status", will_payload="offline", will_qos=1, will_retain=true ) 正常情况: 客户端 → DISCONNECT → Broker (遗嘱不会发送) 异常断线: 客户端 ×××××× 网络中断 Broker 检测到超时 → 自动发布遗嘱消息 "offline" 应用场景: - 设备在线状态监控 - 异常报警
五、在 Linux 上测试 MQTT
5.1 安装 Mosquitto(最流行的 MQTT Broker)
# Ubuntu/Debian sudo apt update sudo apt install mosquitto mosquitto-clients # 启动 Broker sudo systemctl start mosquitto sudo systemctl enable mosquitto # 查看状态 sudo systemctl status mosquitto # 默认监听端口:1883
5.2 命令行测试
终端 1:启动订阅者
# 订阅单个主题 mosquitto_sub -h localhost -t "test/topic" # 订阅所有主题(# 通配符) mosquitto_sub -h localhost -t "#" -v # 参数说明: # -h: Broker 地址 # -t: 主题 # -v: 显示主题名 # -q: QoS 等级(0/1/2)
终端 2:发布消息
# 发布简单消息 mosquitto_pub -h localhost -t "test/topic" -m "Hello MQTT" # 发布保留消息 mosquitto_pub -h localhost -t "status/light" -m "ON" -r # 发布 QoS 1 消息 mosquitto_pub -h localhost -t "sensor/temp" -m "25.5" -q 1 # 参数说明: # -m: 消息内容 # -r: 保留消息 # -q: QoS 等级
示例:模拟温度传感器
# 终端1:订阅温度数据 mosquitto_sub -h localhost -t "home/+/temperature" -v # 终端2:发布温度数据 mosquitto_pub -h localhost -t "home/livingroom/temperature" -m "22.5" mosquitto_pub -h localhost -t "home/bedroom/temperature" -m "21.8" mosquitto_pub -h localhost -t "home/kitchen/temperature" -m "24.3" # 终端1 会收到所有消息: # home/livingroom/temperature 22.5 # home/bedroom/temperature 21.8 # home/kitchen/temperature 24.3
六、Qt 实现 MQTT 客户端
6.1 环境准备
# 检查 Qt 是否包含 MQTT 模块(Qt 5.12+) qmake -query | grep Mqtt # 如果没有,安装 sudo apt install libqt5mqtt5 libqt5mqtt5-dev # 或者从源码编译 Qt MQTT 模块
.pro 文件:
QT += core mqtt CONFIG += c++17
6.2 基础示例:订阅和发布
// main.cpp
#include <QCoreApplication>
#include <QMqttClient>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// ========== 创建 MQTT 客户端 ==========
QMqttClient *client = new QMqttClient(&app);
// 设置 Broker 地址和端口
client->setHostname("localhost"); // 或者 "192.168.1.100"
client->setPort(1883); // 默认端口
// 可选:设置客户端 ID(唯一标识)
client->setClientId("Qt_MQTT_Client_001");
// 可选:设置用户名密码(如果 Broker 需要认证)
// client->setUsername("user");
// client->setPassword("password");
// ========== 连接状态变化处理 ==========
QObject::connect(client, &QMqttClient::connected, [=]() {
qDebug() << "✓ 已连接到 Broker";
// 连接成功后订阅主题
QMqttSubscription *subscription = client->subscribe("sensor/temperature", 1); // QoS=1
if (!subscription) {
qWarning() << "订阅失败";
return;
}
qDebug() << "✓ 已订阅主题: sensor/temperature";
// 接收消息
QObject::connect(subscription, &QMqttSubscription::messageReceived,
[](const QMqttMessage &msg) {
qDebug() << "收到消息:";
qDebug() << " 主题:" << msg.topic().name();
qDebug() << " 内容:" << msg.payload();
qDebug() << " QoS:" << msg.qos();
qDebug() << " 保留:" << msg.retain();
});
});
QObject::connect(client, &QMqttClient::disconnected, [=]() {
qDebug() << "× 已断开连接";
});
QObject::connect(client, &QMqttClient::errorChanged, [=](QMqttClient::ClientError error) {
qWarning() << "错误:" << error;
});
// ========== 连接到 Broker ==========
client->connectToHost();
// ========== 5秒后发布一条消息 ==========
QTimer::singleShot(5000, [=]() {
qDebug() << "\n发布消息...";
qint32 msgId = client->publish(
QMqttTopicName("sensor/temperature"), // 主题
"25.5", // 消息内容
1, // QoS
false // 不保留
);
if (msgId == -1) {
qWarning() << "发布失败";
} else {
qDebug() << "✓ 消息已发布,ID:" << msgId;
}
});
return app.exec();
}
编译运行:
qmake make ./mqtt_example # 期望输出: # ✓ 已连接到 Broker # ✓ 已订阅主题: sensor/temperature # # 发布消息... # ✓ 消息已发布,ID: 1 # 收到消息: # 主题: sensor/temperature # 内容: 25.5 # QoS: 1 # 保留: false
6.3 完整的 MQTT 管理类
// mqttmanager.h
#ifndef MQTTMANAGER_H
#define MQTTMANAGER_H
#include <QObject>
#include <QMqttClient>
#include <QMqttSubscription>
#include <QTimer>
class MqttManager : public QObject
{
Q_OBJECT
public:
explicit MqttManager(const QString &broker = "localhost",
quint16 port = 1883,
QObject *parent = nullptr);
~MqttManager();
// 连接管理
void connectToBroker();
void disconnectFromBroker();
bool isConnected() const;
// 订阅管理
void subscribe(const QString &topic, quint8 qos = 0);
void unsubscribe(const QString &topic);
// 发布消息
qint32 publish(const QString &topic, const QByteArray &message,
quint8 qos = 0, bool retain = false);
// 设置遗嘱消息
void setWillMessage(const QString &topic, const QByteArray &message,
quint8 qos = 0, bool retain = true);
signals:
void connected();
void disconnected();
void errorOccurred(QString error);
void messageReceived(QString topic, QByteArray message);
private slots:
void onConnected();
void onDisconnected();
void onErrorChanged(QMqttClient::ClientError error);
private:
QMqttClient *m_client;
QString m_broker;
quint16 m_port;
QTimer *m_reconnectTimer;
QList<QMqttSubscription*> m_subscriptions;
};
#endif
// mqttmanager.cpp
#include "mqttmanager.h"
#include <QDebug>
MqttManager::MqttManager(const QString &broker, quint16 port, QObject *parent)
: QObject(parent), m_broker(broker), m_port(port)
{
m_client = new QMqttClient(this);
m_client->setHostname(m_broker);
m_client->setPort(m_port);
m_client->setClientId(QString("Qt_MQTT_%1").arg(QDateTime::currentMSecsSinceEpoch()));
// 连接信号
connect(m_client, &QMqttClient::connected,
this, &MqttManager::onConnected);
connect(m_client, &QMqttClient::disconnected,
this, &MqttManager::onDisconnected);
connect(m_client, &QMqttClient::errorChanged,
this, &MqttManager::onErrorChanged);
// 自动重连定时器
m_reconnectTimer = new QTimer(this);
m_reconnectTimer->setInterval(5000); // 5秒重连一次
connect(m_reconnectTimer, &QTimer::timeout, [this]() {
if (m_client->state() == QMqttClient::Disconnected) {
qDebug() << "尝试重新连接...";
m_client->connectToHost();
}
});
}
MqttManager::~MqttManager()
{
disconnectFromBroker();
}
void MqttManager::connectToBroker()
{
if (m_client->state() == QMqttClient::Connected) {
qWarning() << "已经连接到 Broker";
return;
}
qDebug() << "连接到 MQTT Broker:" << m_broker << ":" << m_port;
m_client->connectToHost();
m_reconnectTimer->start();
}
void MqttManager::disconnectFromBroker()
{
m_reconnectTimer->stop();
if (m_client->state() == QMqttClient::Connected) {
m_client->disconnectFromHost();
}
}
bool MqttManager::isConnected() const
{
return m_client->state() == QMqttClient::Connected;
}
void MqttManager::subscribe(const QString &topic, quint8 qos)
{
if (!isConnected()) {
qWarning() << "未连接,无法订阅";
return;
}
QMqttSubscription *subscription = m_client->subscribe(topic, qos);
if (!subscription) {
qWarning() << "订阅失败:" << topic;
return;
}
qDebug() << "✓ 已订阅:" << topic << "QoS:" << qos;
// 保存订阅对象
m_subscriptions.append(subscription);
// 接收消息
connect(subscription, &QMqttSubscription::messageReceived,
[this](const QMqttMessage &msg) {
emit messageReceived(msg.topic().name(), msg.payload());
});
}
void MqttManager::unsubscribe(const QString &topic)
{
m_client->unsubscribe(topic);
qDebug() << "取消订阅:" << topic;
}
qint32 MqttManager::publish(const QString &topic, const QByteArray &message,
quint8 qos, bool retain)
{
if (!isConnected()) {
qWarning() << "未连接,无法发布";
return -1;
}
qint32 msgId = m_client->publish(QMqttTopicName(topic), message, qos, retain);
if (msgId == -1) {
qWarning() << "发布失败:" << topic;
} else {
qDebug() << "✓ 发布消息:" << topic << "内容:" << message;
}
return msgId;
}
void MqttManager::setWillMessage(const QString &topic, const QByteArray &message,
quint8 qos, bool retain)
{
m_client->setWillTopic(topic);
m_client->setWillMessage(message);
m_client->setWillQoS(qos);
m_client->setWillRetain(retain);
qDebug() << "设置遗嘱消息:" << topic << "=" << message;
}
void MqttManager::onConnected()
{
qDebug() << "✓ 已连接到 MQTT Broker";
m_reconnectTimer->stop();
emit connected();
}
void MqttManager::onDisconnected()
{
qDebug() << "× 已断开连接";
m_reconnectTimer->start(); // 启动自动重连
emit disconnected();
}
void MqttManager::onErrorChanged(QMqttClient::ClientError error)
{
QString errorStr;
switch (error) {
case QMqttClient::NoError:
return;
case QMqttClient::InvalidProtocolVersion:
errorStr = "协议版本无效";
break;
case QMqttClient::IdRejected:
errorStr = "客户端 ID 被拒绝";
break;
case QMqttClient::ServerUnavailable:
errorStr = "服务器不可用";
break;
case QMqttClient::BadUsernameOrPassword:
errorStr = "用户名或密码错误";
break;
case QMqttClient::NotAuthorized:
errorStr = "未授权";
break;
case QMqttClient::TransportInvalid:
errorStr = "传输层无效";
break;
default:
errorStr = "未知错误";
}
qWarning() << "MQTT 错误:" << errorStr;
emit errorOccurred(errorStr);
}
使用示例:
// main.cpp
#include <QCoreApplication>
#include "mqttmanager.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
MqttManager mqtt("localhost", 1883);
// 设置遗嘱消息
mqtt.setWillMessage("device/status", "offline", 1, true);
// 连接信号
QObject::connect(&mqtt, &MqttManager::connected, [&mqtt]() {
qDebug() << "连接成功!";
// 订阅主题
mqtt.subscribe("sensor/#", 1); // 订阅所有传感器数据
mqtt.subscribe("command/+", 2); // 订阅所有命令(QoS 2)
// 发布在线状态
mqtt.publish("device/status", "online", 1, true);
});
QObject::connect(&mqtt, &MqttManager::messageReceived,
[](QString topic, QByteArray message) {
qDebug() << "\n收到消息:";
qDebug() << " 主题:" << topic;
qDebug() << " 内容:" << message;
});
// 连接到 Broker
mqtt.connectToBroker();
// 定时发送数据
QTimer *timer = new QTimer(&app);
QObject::connect(timer, &QTimer::timeout, [&mqtt]() {
static int count = 0;
QString data = QString::number(20 + count % 10);
mqtt.publish("sensor/temperature", data.toUtf8(), 0);
count++;
});
timer->start(2000); // 每2秒发送一次
return app.exec();
}
七、实战项目:ADS-B 数据通过 MQTT 传输
将你的 ADS-B 接收机数据通过 MQTT 发布到网络。
7.1 项目结构
ADS-B Receiver → 解析数据 → MQTT Publisher → Broker → MQTT Subscriber → Web/App
7.2 发布端(集成到你的项目)
// adsb_mqtt_publisher.h
#include "mqttmanager.h"
#include "tpublic.h" // 你的 ADS-B 数据结构
class ADSBMqttPublisher : public QObject
{
Q_OBJECT
public:
explicit ADSBMqttPublisher(QObject *parent = nullptr);
public slots:
void onAircraftDataReceived(const Dev_b &aircraft);
private:
MqttManager *m_mqtt;
};
// adsb_mqtt_publisher.cpp
#include "adsb_mqtt_publisher.h"
#include <QJsonDocument>
#include <QJsonObject>
ADSBMqttPublisher::ADSBMqttPublisher(QObject *parent)
: QObject(parent)
{
m_mqtt = new MqttManager("localhost", 1883, this);
connect(m_mqtt, &MqttManager::connected, [this]() {
qDebug() << "MQTT 已连接,开始发布 ADS-B 数据";
m_mqtt->publish("adsb/status", "online", 1, true);
});
m_mqtt->connectToBroker();
}
void ADSBMqttPublisher::onAircraftDataReceived(const Dev_b &aircraft)
{
// 构造 JSON 格式的消息
QJsonObject json;
json["id"] = QString::number(aircraft.id, 16).toUpper();
json["lat"] = aircraft.lat / 100000.0;
json["lon"] = aircraft.lon / 100000.0;
json["alt"] = aircraft.alt;
json["speed"] = aircraft.speed;
json["track"] = aircraft.track;
json["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
QJsonDocument doc(json);
QByteArray message = doc.toJson(QJsonDocument::Compact);
// 发布到主题:adsb/aircraft/<ID>
QString topic = QString("adsb/aircraft/%1").arg(aircraft.id, 6, 16, QChar('0'));
m_mqtt->publish(topic, message, 0, false);
// 同时发布到汇总主题
m_mqtt->publish("adsb/all", message, 0, false);
}
7.3 订阅端(监控所有飞机)
// mqtt_monitor.cpp
#include <QCoreApplication>
#include "mqttmanager.h"
#include <QJsonDocument>
#include <QJsonObject>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
MqttManager mqtt("localhost", 1883);
QObject::connect(&mqtt, &MqttManager::connected, [&mqtt]() {
// 订阅所有飞机数据
mqtt.subscribe("adsb/aircraft/+", 0);
});
QObject::connect(&mqtt, &MqttManager::messageReceived,
[](QString topic, QByteArray message) {
// 解析 JSON
QJsonDocument doc = QJsonDocument::fromJson(message);
QJsonObject json = doc.object();
qDebug() << "\n=== 飞机数据 ===";
qDebug() << "ID:" << json["id"].toString();
qDebug() << "经度:" << json["lon"].toDouble();
qDebug() << "纬度:" << json["lat"].toDouble();
qDebug() << "高度:" << json["alt"].toInt() << "m";
qDebug() << "速度:" << json["speed"].toInt() << "km/h";
qDebug() << "时间:" << json["timestamp"].toString();
});
mqtt.connectToBroker();
return app.exec();
}
八、MQTT 进阶主题
8.1 Clean Session(清理会话)
// 不清理会话(默认) m_client->setCleanSession(false); 好处: - 离线消息会被 Broker 保存 - 重连后自动恢复订阅 - 适合不稳定网络 // 清理会话 m_client->setCleanSession(true); 好处: - 每次都是全新开始 - 不保存历史状态 - 适合临时客户端
8.2 Keep Alive(心跳)
// 设置心跳间隔(秒) m_client->setKeepAlive(60); // 60秒 作用: - 定时发送 PINGREQ 包 - Broker 回复 PINGRESP - 如果超时 1.5 倍 Keep Alive 时间没有响应,认为断线
8.3 SSL/TLS 加密连接
#include <QSslConfiguration> // 配置 SSL QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setProtocol(QSsl::TlsV1_2OrLater); m_client->connectToHostEncrypted(sslConfig); m_client->setPort(8883); // MQTT over SSL 默认端口
九、常见问题
问题 1:连接失败
// 检查 Broker 是否运行 sudo systemctl status mosquitto // 检查端口是否监听 netstat -tuln | grep 1883 // 检查防火墙 sudo ufw allow 1883
问题 2:收不到消息
// 确认已订阅 subscription->state() == QMqttSubscription::Subscribed // 确认主题匹配 订阅: "sensor/temp" 发布: "sensor/temperature" // 不匹配! // 确认 QoS 订阅 QoS=0,发布 QoS=2 // 会降级到 QoS 0
问题 3:消息丢失
原因:QoS 设置不当 解决: - 重要消息使用 QoS 1 或 2 - 发布端和订阅端都要设置合适的 QoS - 使用保留消息保存最新状态
十、学习路线
第 1 周:基础掌握
- 安装 Mosquitto 并测试
- 理解发布/订阅模式
- 掌握主题和通配符
- 理解 QoS 三个等级
第 2 周:Qt 编程
- Qt MQTT 基础示例
- 完整的 MqttManager 类
- 处理连接/断线/重连
- 理解保留消息和遗嘱
第 3 周:项目实战
- 集成 MQTT 到 ADS-B 项目
- JSON 格式数据传输
- 远程监控和控制
- 数据持久化(结合数据库)
总结
MQTT 相比 Modbus 和 CAN:
-
✅ 更灵活:发布/订阅模式,不需要知道对方地址
-
✅ 更现代:原生支持云平台和物联网
-
✅ 更可靠:三级 QoS 保证
-
✅ 更易用:Qt 有完整的 MQTT 支持
下一步: 你想深入学习哪个方向?
-
MQTT 桥接(Broker 之间的连接)
-
MQTT + WebSocket(浏览器直接连接)
-
MQTT 集群部署(高可用)
-
或者继续学习其他协议(OPC UA, CoAP)?
需要我详细讲解某个部分吗?
1425

被折叠的 条评论
为什么被折叠?



