C++/Qt + Mqtt协议 实现客户端订阅/发布功能
目录一、UI 构建setupUI二、信号槽绑定setupConnections三、连接/断开 BrokeronConnectClicked四、 订阅主题onSubscribeClicked五、取消订阅onUnsubscribeClicked六、发布普通消息onPublishClicked七、发布保留消息onPublishRetainClicked八、连接状态变化处理onStateChanged九、连接成功处理onConnected十、断开连接处理onDisconnected十一、 接收消息处理onMessageReceived十二、消息发送确认onMessageSent十三、错误处理onErrorChanged一、UI 构建setupUIvoid MainWindow::setupUI() { // --- 中央部件和主布局 --- QWidget *central new QWidget(this); setCentralWidget(central); QVBoxLayout *mainLayout new QVBoxLayout(central); // 1. 连接配置区域 m_groupConnect new QGroupBox(tr(连接配置), this); QFormLayout *formLayout new QFormLayout(m_groupConnect); // Broker 地址默认 localhost m_editHost new QLineEdit(127.0.0.1, this); formLayout-addRow(tr(Broker 地址:), m_editHost); // 端口号MQTT 默认 1883 m_spinPort new QSpinBox(this); m_spinPort-setRange(1, 65535); m_spinPort-setValue(1883); formLayout-addRow(tr(端口:), m_spinPort); // 客户端 IDMQTT 协议要求每个客户端有唯一 ID // QUuid 生成一个全局唯一标识符避免多客户端冲突 m_editClientId new QLineEdit(QUuid::createUuid().toString(QUuid::WithoutBraces), this); formLayout-addRow(tr(客户端 ID:), m_editClientId); // 用户名和密码可选用于 Broker 认证 m_editUsername new QLineEdit(this); m_editUsername-setPlaceholderText(可选); formLayout-addRow(tr(用户名:), m_editUsername); m_editPassword new QLineEdit(this); m_editPassword-setPlaceholderText(可选); m_editPassword-setEchoMode(QLineEdit::Password); formLayout-addRow(tr(密码:), m_editPassword); // QoS 等级选择 // QoS 0: 最多一次发完不管可能丢 // QoS 1: 至少一次保证收到可能重复 // QoS 2: 恰好一次保证收到且不重复开销最大 m_comboQos new QComboBox(this); m_comboQos-addItem(QoS 0 - 最多一次, 0); m_comboQos-addItem(QoS 1 - 至少一次, 1); m_comboQos-addItem(QoS 2 - 恰好一次, 2); formLayout-addRow(tr(QoS 等级:), m_comboQos); // 连接/断开按钮 状态标签 QHBoxLayout *connBtnLayout new QHBoxLayout(); m_btnConnect new QPushButton(tr(连接), this); m_btnConnect-setMinimumWidth(100); connBtnLayout-addWidget(m_btnConnect); m_labelStatus new QLabel(tr(状态: 未连接), this); m_labelStatus-setStyleSheet(color: red; font-weight: bold;); connBtnLayout-addWidget(m_labelStatus); connBtnLayout-addStretch(); formLayout-addRow(connBtnLayout); mainLayout-addWidget(m_groupConnect); // 2. 订阅区域 m_groupSubscribe new QGroupBox(tr(订阅主题), this); QHBoxLayout *subLayout new QHBoxLayout(m_groupSubscribe); m_editSubTopic new QLineEdit(test/topic, this); m_editSubTopic-setPlaceholderText(输入主题名如 sensor/temp); subLayout-addWidget(m_editSubTopic); m_btnSubscribe new QPushButton(tr(订阅), this); subLayout-addWidget(m_btnSubscribe); m_btnUnsubscribe new QPushButton(tr(取消订阅), this); subLayout-addWidget(m_btnUnsubscribe); mainLayout-addWidget(m_groupSubscribe); // 3. 发布区域 m_groupPublish new QGroupBox(tr(发布消息), this); QHBoxLayout *pubLayout new QHBoxLayout(m_groupPublish); m_editPubTopic new QLineEdit(test/topic, this); m_editPubTopic-setPlaceholderText(发布主题); pubLayout-addWidget(m_editPubTopic, 2); m_editMessage new QLineEdit(Hello MQTT, this); m_editMessage-setPlaceholderText(消息内容); pubLayout-addWidget(m_editMessage, 3); m_btnPublish new QPushButton(tr(发布), this); pubLayout-addWidget(m_btnPublish); m_btnPubRetain new QPushButton(tr(发布(保留)), this); m_btnPubRetain-setToolTip(保留消息Broker 会保存最后一条新订阅者立即收到); pubLayout-addWidget(m_btnPubRetain); mainLayout-addWidget(m_groupPublish); // 4. 消息日志区域 QGroupBox *logGroup new QGroupBox(tr(消息日志), this); QVBoxLayout *logLayout new QVBoxLayout(logGroup); m_logText new QTextEdit(this); m_logText-setReadOnly(true); // 只读不允许用户编辑 logLayout-addWidget(m_logText); mainLayout-addWidget(logGroup, 1); // 日志区域占剩余空间 // 窗口基本设置 setWindowTitle(tr(MQTT 学习 Demo - QtMqtt)); resize(700, 650); }二、信号槽绑定setupConnectionsvoid MainWindow::setupConnections() { // --- 按钮点击信号 --- connect(m_btnConnect, QPushButton::clicked, this, MainWindow::onConnectClicked); connect(m_btnSubscribe, QPushButton::clicked, this, MainWindow::onSubscribeClicked); //订阅 connect(m_btnUnsubscribe, QPushButton::clicked, this, MainWindow::onUnsubscribeClicked); //取消订阅 connect(m_btnPublish, QPushButton::clicked, this, MainWindow::onPublishClicked); //发布普通消息 connect(m_btnPubRetain, QPushButton::clicked, this, MainWindow::onPublishRetainClicked); // 发布保留消息 // --- QMqttClient 核心信号重点学习这些 --- // 1. stateChanged: 连接状态变化时触发 // ClientState 枚举值: Disconnected0, Connecting1, Connected2 connect(m_client, QMqttClient::stateChanged, this, MainWindow::onStateChanged); // 2. connected: 成功连接到 Broker 时触发 connect(m_client, QMqttClient::connected, this, MainWindow::onConnected); // 3. disconnected: 与 Broker 断开连接时触发 connect(m_client, QMqttClient::disconnected, this, MainWindow::onDisconnected); // 4. messageReceived: 收到订阅主题的消息时触发 // 参数: QByteArray 消息内容, QMqttTopicName 主题名 connect(m_client, QMqttClient::messageReceived, this, MainWindow::onMessageReceived); // 5. messageSent: 消息成功发布到 Broker 时触发 // 参数: qint32 消息 ID connect(m_client, QMqttClient::messageSent, this, MainWindow::onMessageSent); // 6. errorChanged: 发生错误时触发 connect(m_client, QMqttClient::errorChanged, this, MainWindow::onErrorChanged); }三、连接/断开 BrokeronConnectClickedvoid MainWindow::onConnectClicked() { if (m_client-state() QMqttClient::Connected) { // 已连接 → 执行断开 m_client-disconnectFromHost(); return; } // --- 配置连接参数 --- m_client-setHostname(m_editHost-text()); m_client-setPort(m_spinPort-value()); m_client-setClientId(m_editClientId-text()); // 设置认证信息如果填了用户名和密码 if (!m_editUsername-text().isEmpty()) { m_client-setUsername(m_editUsername-text()); } if (!m_editPassword-text().isEmpty()) { m_client-setPassword(m_editPassword-text()); } // 设置 Keep Alive心跳间隔单位秒 // 客户端必须在此时间内至少发送一次消息或 PING 请求否则 Broker 会认为客户端已死并断开连接 // 默认 60 秒设为 30 秒更保险 m_client-setKeepAlive(30); /* * 什么是遗嘱消息 遗嘱消息是 MQTT 客户端在连接 Broker 时提前“立下遗嘱”指定一个主题和一条消息。如果该客户端异常断开例如网络故障、电源掉电、程序崩溃等而不是主动发送 DISCONNECT 报文 Broker 就会自动将这条遗嘱消息发布到指定的主题上通知其他订阅者。 类比一个人客户端在进入危险环境前提前写好遗书遗嘱消息交给公证人Broker并说明如果自己意外死亡异常断开就把遗书公布给指定的人订阅者。 */ // 配置遗嘱消息Last Will // 作用当客户端异常断开时Broker 自动发布这条消息通知其他客户端 m_client-setWillTopic(client/status);//设置遗嘱消息的发布主题。当客户端异常断开时Broker 会向 client/status 这个主题发布一条消息。 m_client-setWillMessage(客户端离线); m_client-setWillQoS(1); m_client-setWillRetain(true); // 标记为保留消息。Broker 会永久保存这条消息后续新订阅 client/status 主题的客户端立即收到这条离线通知而不需要等待下一次事件发生。 appendLog(正在连接到 Broker m_editHost-text() : QString::number(m_spinPort-value()) ...); // 发起连接异步操作结果通过 connected/disconnected 信号通知 m_client-connectToHost(); }四、 订阅主题onSubscribeClickedvoid MainWindow::onSubscribeClicked() { QString topic m_editSubTopic-text().trimmed(); if (topic.isEmpty()) { QMessageBox::warning(this, tr(警告), tr(请输入要订阅的主题名)); return; } // 获取用户选择的 QoS 等级 int qos m_comboQos-currentData().toInt(); // QMqttClient::subscribe() 返回 QMqttSubscription 对象 // 可通过它监控订阅状态如是否成功、QoS 协商结果等 /*MQTT 主题过滤器 可以包含通配符 单层通配符匹配任意一个层级。 #多层通配符匹配任意多个层级必须放在最后。 当你构造 QMqttTopicFilter(topic) 时如果传入的 topic 不包含通配符它就是一个精确的主题过滤器只匹配该主题本身如果包含通配符它会匹配所有符合条件的主题。*/ QMqttSubscription *sub m_client-subscribe(QMqttTopicFilter(topic), qos); // QMqttClient::subscribe()订阅方法返回订阅对象 // 可选监听订阅状态变化 connect(sub, QMqttSubscription::stateChanged, this, [this, topic](QMqttSubscription::SubscriptionState state) { if (state QMqttSubscription::Subscribed) { appendLog(✓ 订阅成功: topic); } else if (state QMqttSubscription::Unsubscribed) { appendLog(✗ 已取消订阅: topic); } }); appendLog(正在订阅主题: topic (QoS QString::number(qos) )); }五、取消订阅onUnsubscribeClickedvoid MainWindow::onUnsubscribeClicked() { QString topic m_editSubTopic-text().trimmed(); if (topic.isEmpty()) { QMessageBox::warning(this, tr(警告), tr(请输入要取消订阅的主题名)); return; } // QMqttClient::unsubscribe() 取消对指定主题的订阅 m_client-unsubscribe(QMqttTopicFilter(topic)); appendLog(正在取消订阅: topic); }六、发布普通消息onPublishClickedvoid MainWindow::onPublishClicked() { QString topic m_editPubTopic-text().trimmed(); QString message m_editMessage-text().trimmed(); if (topic.isEmpty() || message.isEmpty()) { QMessageBox::warning(this, tr(警告), tr(请输入主题和消息内容)); return; } int qos m_comboQos-currentData().toInt(); // QMqttClient::publish() 发布消息 // 参数: 主题名, 消息内容, QoS 等级, 是否保留默认false // 返回值: qint32 消息 ID可用于跟踪发布状态 qint32 msgId m_client-publish( QMqttTopicName(topic), // 主题 message.toUtf8(), // 消息体转为 UTF-8 字节 qos // QoS 等级 ); appendLog(发布消息 - 主题: topic , QoS: QString::number(qos) , ID: QString::number(msgId)); }七、发布保留消息onPublishRetainClickedvoid MainWindow::onPublishRetainClicked() { QString topic m_editPubTopic-text().trimmed(); QString message m_editMessage-text().trimmed(); if (topic.isEmpty() || message.isEmpty()) { QMessageBox::warning(this, tr(警告), tr(请输入主题和消息内容)); return; } int qos m_comboQos-currentData().toInt(); // 保留消息Broker 会保存这条消息后续订阅该主题的新客户端会立即收到 // 典型用途设备状态、配置信息等最新值 qint32 msgId m_client-publish( QMqttTopicName(topic), message.toUtf8(), qos, true // retain true标记为保留消息 ); appendLog(发布保留消息 - 主题: topic , ID: QString::number(msgId)); }八、连接状态变化处理onStateChangedvoid MainWindow::onStateChanged(QMqttClient::ClientState state) { // 根据当前状态更新 UI 显示 QString stateText; switch (state) { case QMqttClient::Disconnected: stateText 未连接; break; case QMqttClient::Connecting: stateText 正在连接...; break; case QMqttClient::Connected: stateText 已连接; break; } m_labelStatus-setText(状态: stateText); // 连接中显示黄色已连接显示绿色未连接显示红色 if (state QMqttClient::Connected) { m_labelStatus-setStyleSheet(color: green; font-weight: bold;); } else if (state QMqttClient::Connecting) { m_labelStatus-setStyleSheet(color: orange; font-weight: bold;); } else { m_labelStatus-setStyleSheet(color: red; font-weight: bold;); } }九、连接成功处理onConnectedvoid MainWindow::onConnected() { appendLog( 已连接到 Broker ); m_btnConnect-setText(断开); // 连接成功后可以发布一条上线通知 m_client-publish(QMqttTopicName(client/status), 客户端在线, 1, true); }十、断开连接处理onDisconnectedvoid MainWindow::onDisconnected() { appendLog( 与 Broker 断开连接 ); m_btnConnect-setText(连接); }十一、 接收消息处理onMessageReceivedvoid MainWindow::onMessageReceived(const QByteArray message, const QMqttTopicName topic) { // 收到消息时的核心处理逻辑 // topic.name() → 获取主题名字符串 // message → 获取消息内容QByteArray可转 QString QString timeStr QDateTime::currentDateTime().toString(HH:mm:ss); appendLog([ timeStr ] 收到消息 ← 主题: topic.name() | 内容: QString::fromUtf8(message)); }十二、消息发送确认onMessageSentvoid MainWindow::onMessageSent(qint32 id) { // 消息成功发布到 Broker 后触发 // 注意QoS 0 时可能不会触发此信号因为不需要确认 appendLog(消息发送确认 ✓ (ID: QString::number(id) )); }十三、错误处理onErrorChangedvoid MainWindow::onErrorChanged(QMqttClient::ClientError error) { // 错误处理根据错误码判断问题类型 QString errorText; switch (error) { case QMqttClient::NoError: return; // 无错误忽略 // --- 协议层错误MQTT 协议返回 --- case QMqttClient::InvalidProtocolVersion: errorText 协议版本不支持; break; case QMqttClient::IdRejected: errorText 客户端 ID 被拒绝可能太长或格式不对; break; case QMqttClient::ServerUnavailable: errorText Broker 不可用; break; case QMqttClient::BadUsernameOrPassword: errorText 用户名或密码错误; break; case QMqttClient::NotAuthorized: errorText 认证失败无权限; break; // --- 传输层错误网络问题 --- case QMqttClient::TransportInvalid: errorText 传输层错误网络断开/连接失败; break; case QMqttClient::ProtocolViolation: errorText 协议违规消息格式不正确; break; case QMqttClient::UnknownError: errorText 未知错误; break; case QMqttClient::Mqtt5SpecificError: errorText MQTT 5.0 特有错误; break; default: errorText 未知错误码: QString::number(error); break; } appendLog(⚠ 错误: errorText); }

相关新闻

千笔AI:深度学习驱动的学术写作智能辅助平台

千笔AI:深度学习驱动的学术写作智能辅助平台

1. 项目概述:学术写作的智能化革命千笔AI作为一款面向学术论文写作的智能辅助平台,正在改变传统学术研究的范式。这个平台的核心价值在于通过深度学习技术,为研究者提供从选题到发表的全程智能化支持。不同于市面上常见的论文查重或格式调整工…

2026/10/22 3:28:11 阅读更多 →
3个步骤让魔兽争霸3在现代电脑上完美运行:WarcraftHelper兼容性修复指南

3个步骤让魔兽争霸3在现代电脑上完美运行:WarcraftHelper兼容性修复指南

3个步骤让魔兽争霸3在现代电脑上完美运行:WarcraftHelper兼容性修复指南 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 你是否还在为魔兽…

2026/10/25 20:21:02 阅读更多 →
【企业微信】基于iPad协议的联系人管理接口:支持内外部联系人操作与多渠道添加功能

【企业微信】基于iPad协议的联系人管理接口:支持内外部联系人操作与多渠道添加功能

一、简要总结 本文档是企业微信 iPad 协议接口的联系人模块功能说明,共包含20 个细分操作接口,所有接口统一采用POST请求、ContentType:application/json格式,uuid 为唯一必传核心参数,服务域名以[172.0.0.1:8083](17…

2026/10/25 18:59:55 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/27 17:33:32 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/27 4:06:49 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/27 7:21:53 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/27 18:20:50 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/27 6:20:47 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/27 4:06:50 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 13:55:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 7:21:50 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 19:19:37 阅读更多 →