简介:这份资源面向在 Qt 环境下开发物联网通信功能的开发者,尤其是需要将 MQTT 协议接入阿里云物联网平台的初学者与中级工程师。它基于 Qt 5.10.1 与 minGW 5.10.1 编译环境,调用第三方 qmqtt 库,实现了 Connect、Publish、Subscribe、Unsubscribe 等核心功能,可直接作为 Qt 移植 MQTT 协议与云端对接的参考工程。压缩包共 28 个文件,约 1.91MB,包含 20 个头文件、2 个 cpp 源文件、1 个 ui 界面文件、1 个 pro 工程文件,以及 2 个 dll 与 2 个 a 静态库文件,覆盖 qmqtt 客户端、网络接口、消息路由、SSL 套接字等模块,结构完整便于二次开发。目前已有 2115 人学习下载。读者可从中获取 Qt 集成 MQTT 的工程组织方式、阿里云物联网平台连接与消息收发的实现思路,以及第三方库的编译与调用方法,适合作为物联网项目通信层的起步模板与排错参考。
1. 拿到一个 Qt5.10.1 的 MQTT 工程包,先别急着编译
如果你正在做 Qt 上位机对接物联网平台,大概率会遇到这个场景:设备端数据已经跑通,云端控制台也能看到消息流转,但桌面端要自己写一个 MQTT 客户端,从零搭环境、找库、配编译链,光是把 qmqtt 或 QtMqtt 编进 Qt5.10.1 就得折腾半天。这个压缩包解决的就是这件事——它把 Qt5.10.1、MQTT 协议库、minGW5.10.1 编译环境打包在一起,省掉你单独找库、对版本、配 kit 的时间。适合两类人:一是刚接触 Qt 又要做物联网对接的开发者,二是手头有旧版 Qt 项目、不想升级到 Qt6 但需要 MQTT 能力的维护者。下面按“先跑通、再改参数、最后避坑”的顺序拆一遍。
2. 环境解压与 Qt Creator 套件配置:把编译链对齐
2.1 解压后的目录结构与版本匹配逻辑
拿到压缩包先别双击 .pro 文件。Qt 工程能不能一次编译通过,八成取决于套件(Kit)里的编译器、Qt 版本、调试器三者是否来自同一套安装。这个包里 minGW5.10.1 是随 Qt5.10.1 官方安装器一起分发的 32 位 GCC 工具链,Qt 库本身也是用这个编译器构建的。如果你机器上已经装了别的 Qt 版本,Qt Creator 可能会自动选错 Kit,导致链接阶段报一堆undefined reference to _imp__...。
常见做法是:解压到一个纯英文、无空格的路径,比如D:\dev\qt-mqtt-demo。路径里有中文或空格,minGW 的 make 在某些 Windows 版本上会解析失败,这是血泪经验,不是玄学。
解压后大致会看到:
| 目录/文件 | 作用 |
|---|---|
Qt5.10.1/ | Qt 库、头文件、minGW 工具链 |
mqtt/或qmqtt/ | MQTT 模块源码或预编译库 |
*.pro | 示例工程文件 |
README或说明文件 | 环境变量与 Kit 配置提示 |
2.2 在 Qt Creator 里手动指定 Kit
打开 Qt Creator,进工具 → 选项 → 构建和运行。在“Qt Versions”页签里,如果列表为空或指向了别的版本,点“添加”,选择解压目录下Qt5.10.1/5.10.1/mingw53_32/bin/qmake.exe。注意,Qt5.10.1 官方配套的 minGW 版本号是 5.3.0 的 32 位变体,社区常叫它 minGW5.10.1,指的是随 Qt5.10.1 分发的那个版本,别去下独立的 minGW 8.x,ABI 不兼容。
接着在“构建套件(Kit)”页签,新建一个 Kit:
- 编译器:C++ 选
Qt5.10.1/Tools/mingw530_32/bin/g++.exe - 调试器:选同目录下的
gdb.exe - Qt Version:选上一步添加的 qmake
- CMake Tool:如果工程用 qmake 就不用管
配置完点 OK,回到工程,在“项目”模式左下角把 Kit 切换成刚建的这个。这一步做完,再执行构建 → 执行 qmake,然后编译。
# 如果你习惯命令行,也可以直接走 qmake + make cd D:\dev\qt-mqtt-demo D:\dev\qt-mqtt-demo\Qt5.10.1\5.10.1\mingw53_32\bin\qmake.exe demo.pro D:\dev\qt-mqtt-demo\Qt5.10.1\Tools\mingw530_32\bin\mingw32-make.exe -j4这两条命令的含义:第一条用指定版本的 qmake 生成 Makefile,避免系统 PATH 里别的 qmake 抢先;第二条用配套的 mingw32-make 并行编译,-j4是四线程,机器核少就改-j2。编译成功后会在debug/或release/下生成 exe。如果报cannot find -lQt5Mqtt,说明 MQTT 库没被正确链接,下一章专门处理。
注意:Qt5.10.1 的 minGW 工具链是 32 位的,生成的 exe 也是 32 位。如果你的系统是 64 位,能跑,但别混用 64 位的第三方库。
3. MQTT 模块接入与阿里云物联网平台参数配置
3.1 qmqtt 与 QtMqtt 的选型差异
Qt 官方直到 Qt5.12 之后才把 MQTT 作为正式模块(QtMqtt)提供,Qt5.10.1 时期主流用的是社区版 qmqtt。这个包里如果带的是 qmqtt 源码,你需要先把它编译成库,再在工程里链接。选 qmqtt 的理由:轻量、纯 Qt 实现、不依赖额外 OpenSSL 也能跑 TCP 明文(但对接云平台必须上 TLS)。如果你拿到的是预编译的.a或.dll,跳过编译,直接在 .pro 里加路径。
在.pro文件里追加:
# 指向 qmqtt 的头文件和库文件 INCLUDEPATH += $$PWD/qmqtt/include LIBS += -L$$PWD/qmqtt/lib -lqmqtt # 如果 qmqtt 依赖网络和 WebSocket,按需加 QT += network websocketsINCLUDEPATH告诉编译器去哪找qmqttclient.h;LIBS里的-L是库目录,-lqmqtt是链接libqmqtt.a或qmqtt.dll的导入库。改完 .pro 必须重新执行 qmake,否则 Qt Creator 不会刷新链接参数。
3.2 对接阿里云物联网平台的连接参数
阿里云物联网平台用 MQTT 接入时,关键参数有五个:ProductKey、DeviceName、DeviceSecret、RegionId、以及由前三者算出的 ClientId/Username/Password。Qt5.10.1 下没有现成的阿里云 SDK,需要自己拼。
常见做法是用 HMAC-SHA1 对clientId + deviceName + productKey做签名,生成 password。下面是一段可抄的拼接逻辑:
// 假设已从配置文件读到 pk, dn, ds QString clientId = "12345|securemode=3,signmethod=hmacsha1|"; QString username = dn + "&" + pk; // 签名内容顺序:clientId 前缀 + deviceName + productKey QString content = "clientId" + "12345" + "deviceName" + dn + "productKey" + pk; QByteArray sign = QMessageAuthenticationCode::hash( content.toUtf8(), ds.toUtf8(), QCryptographicHash::Sha1).toHex(); QString password = sign;参数说明:securemode=3表示 TCP 直连加 TLS;signmethod=hmacsha1是阿里云要求的签名算法;clientId里的12345可以换成任意数字后缀,但同一 ProductKey 下不能重复,否则会互相踢下线。QMessageAuthenticationCode是 Qt5.1 之后自带的,不用额外引 OpenSSL。
连接时用QMQTT::Client:
QMQTT::Client *client = new QMQTT::Client(QHostAddress("${YourInstanceId}.mqtt.iothub.aliyuncs.com"), 1883); client->setClientId(clientId); client->setUsername(username); client->setPassword(password.toUtf8()); client->setKeepAlive(60); client->connectToHost();端口 1883 是明文,正式环境建议用 8883 并加载 CA 证书。setKeepAlive(60)是心跳间隔,阿里云允许 30 到 1200 秒,设太短会频繁重连,设太长会被服务端判定离线。
3.3 订阅与发布主题的格式
阿里云的主题有固定层级:/sys/{productKey}/{deviceName}/thing/event/property/post用于上报属性,/sys/{productKey}/{deviceName}/thing/service/property/set用于接收云端下发。订阅时用client->subscribe(topic, 1),QoS 选 1 保证至少一次送达。发布时构造QMQTT::Message,payload 是 JSON 字符串。
QMQTT::Message msg(0, "/sys/pk123/dn456/thing/event/property/post", "{\"params\":{\"temperature\":25.6}}"); client->publish(msg);注意 payload 里的id字段可以省略,阿里云会自动补。如果发布后控制台看不到日志,先检查 Topic 权限——设备默认只有发布和订阅自己名下主题的权限,跨设备主题会被拒绝。
4. 避坑与排查:编译、连接、心跳三类翻车现场
4.1 编译报错undefined reference to qmqtt::Client::Client
现象:.pro 里已经加了-lqmqtt,但链接阶段还是找不到符号。原因通常是 qmqtt 库的编译器和当前 Kit 的编译器不是同一套,比如库是 MSVC 编的,工程用 minGW。解决:用当前 Kit 的 minGW 重新编译 qmqtt 源码,或者直接找配套的 minGW 版预编译库。验证方法是看库文件大小和扩展名,.a是 minGW,.lib是 MSVC。
4.2 连接返回Connection refused或一直重连
现象:client->connectToHost()后状态在 Connecting 和 Disconnected 之间循环。原因有三个常见点:一是 ClientId 重复,同一设备被别的客户端顶掉;二是签名算错,password 不对,服务端直接断开;三是系统时间偏差超过 15 分钟,TLS 握手失败。解决:先用 MQTT 调试工具(如 MQTTX)用同样参数连一次,确认参数无误;再检查本机时间是否同步;最后看 ClientId 是否带了非法字符。
4.3 心跳超时导致设备离线
现象:设备连上后几分钟就掉线,控制台显示“设备离线”。原因:setKeepAlive设得比实际网络 RTT 还短,或者主线程被阻塞导致心跳包没发出去。解决:把 keepAlive 调到 120 秒以上,并把 MQTT 客户端放到独立线程或确保事件循环不被耗时操作卡住。Qt 的信号槽默认队列连接,如果主线程在跑死循环,心跳定时器不会触发。
4.4 订阅收不到消息
现象:subscribe返回成功,但received信号不触发。原因:Topic 写错层级,比如把/sys/写成/sys,或者 QoS 设成 2 但服务端不支持。解决:用client->subscribe(topic, 1)明确 QoS 1,并在onConnected之后再订阅,不要在构造函数里就订阅——连接还没建立,订阅请求发不出去。
4.5 中文 payload 乱码
现象:发布 JSON 里带中文,云端收到是问号。原因:QMQTT::Message的 payload 默认按QByteArray处理,如果源字符串没转 UTF-8 就会按本地编码发出去。解决:msg.setPayload(jsonString.toUtf8()),接收端用QString::fromUtf8(msg.payload())还原。
5. 进阶:用 Wireshark 抓包验证 MQTT 报文与 TLS 握手
跑通之后,怎么确认发出去的报文真的符合 MQTT 3.1.1 规范?我一般会开 Wireshark 抓回环或网卡,过滤tcp.port == 1883。明文连接下能直接看到 CONNECT、CONNACK、PUBLISH 的十六进制。重点看 CONNECT 报文里的 Keep Alive 字段是不是你设的 60,以及 PUBLISH 的 Topic Length 和 Topic 字符串是否一致。如果走 8883 TLS,Wireshark 只能看到握手和加密记录,这时候要导出的就是Client Hello里的 SNI 是否指向${YourInstanceId}.mqtt.iothub.aliyuncs.com,以及证书链是否完整。
另一个验证手段是在代码里挂QMQTT::Client的connected、disconnected、error三个信号,把错误码打出来。qmqtt 的error信号带QMQTT::ClientError枚举,比如ConnectionRefusedError对应 1,SocketError对应 256 以上。对照枚举表能快速定位是网络层还是协议层的问题。
connect(client, &QMQTT::Client::error, this, [](QMQTT::ClientError e){ qDebug() << "MQTT error code:" << static_cast<int>(e); }); connect(client, &QMQTT::Client::connected, this, [](){ qDebug() << "connected, now subscribe"; });最后说一个我自己的习惯:每次改完 ClientId 或签名逻辑,先不连阿里云,而是连本地mosquittobroker 跑一遍。本地 broker 不校验签名,能快速排除代码层面的拼接错误。等本地通了,再换云端参数。从那以后我每次对接新平台都强制走一遍“本地 broker → 抓包 → 云端”的流程,省下大量在云端日志里翻来翻去的时间。希望帮到你。
本文还有配套的精品资源,点击获取