简介:面向需要在 Windows 下基于 Qt 快速接入阿里云物联网平台的开发者,这份压缩包提供完整的 MQTT 通信示例工程。工程使用 Qt5.10.1 与 minGW5.10.1 编译,调用第三方 QMQTT 库,实现设备连接、发布消息、订阅主题、取消订阅等功能,可直接用于设备数据上云。压缩包共 28 个文件,包括 20 个头文件、2 个动态链接库、2 个静态库、2 个源程序文件,以及界面文件和工程文件,整体大小 1.91MB,头文件覆盖客户端、网络、消息帧、路由等核心模块,库文件已经预先编译,省去自行搭建依赖的步骤。工程自带可视化界面,可在 Qt Creator 中打开调试,也便于将封装库集成到已有项目。已有 2115 人学习下载,对于研究 MQTT 协议移植和物联网对接的技术人员有实用参考价值。
1. QT5.10.1 移植 MQTT:一份源码包背后的完整工程链路
做物联网上位机开发的同行多少都遇到过这个场景:拿了份 QT 写的 MQTT 客户端源码包,一编译全是红字,moc 文件找不到、qmqtt 模块链接失败、minGW 版本对不上。最近我在整理手头一份 QT5.10.1 + MQTT + minGW5.10.1 的完整移植源码包时,把当初踩过的坑全部重新过了一遍。这份资源不是简单的代码堆叠,它把 Qt 5.10.1 环境下 MQTT 协议适配、minGW 编译链匹配和云平台对接做了完整的工程化串联,适合正在做工业网关、数据采集终端、远程监控面板的嵌入式或上位机开发者,也适合刚接触 QT 网络编程的新手用来理解 MQTT 从源码编译到业务联调的全流程。它解决的核心问题是:在 QT5.10.1 上让你不再自己摸索第三方 MQTT 库的路径配置和编译选项,拿过来就能直接出成果。下面我把整个移植过程、对接参数和排坑记录拆开讲清楚。
2. 为什么用 QT5.10.1 + minGW5.10.1:编译链匹配与协议栈选择
2.1 minGW 版本和 QT 版本之间的强绑定关系
很多人移植 MQTT 库失败,第一刀就砍在编译器版本上。QT5.10.1 官方安装包提供了多个编译套件,其中 minGW 版本是 5.3.0 还是 5.10.1 直接决定了你后面能不能正常编译第三方源码。这份源码包采用的是 minGW 5.10.1 32 位套件,它对应的 QT 编译器路径一般在C:\Qt\Qt5.10.1\5.10.1\mingw53_32,注意这里的目录名可能写的是 mingw53_32,但实际工具链版本是 5.10.1。我一开始按惯性思维去匹配 minGW 5.3.0,结果 qmake 生成的 Makefile 链接时大量报错——因为 QT 源码包里预编译的库文件是用特定版本的 minGW 生成的,混用版本直接导致 ABI 不兼容。
在 Windows 环境下,QT 的 qmake 会根据你选择的kit 自动配置编译器路径、qmake 路径和 MAKE 命令。如果你是用命令行走 qmake,务必确认 PATH 环境变量里第一个 minGW 路径是 QT 自带的那个,而不是系统里另外安装的 CodeBlocks 或 Dev-C++ 自带的 minGW。我在这上面翻过一次车:系统里装过 minGW8.1,命令行执行 qmake 后直接调用了错误版本的 g++,编译时出现几百个找不到头文件的报错。怎么定位这个问题?在命令行执行g++ --version,看输出的版本号,QT5.10.1 配套的应该是 gcc 5.3.0 或类似版本,如果看到 8.1.0 或者更高,基本可以断定 PATH 被污染了。
再看第三个匹配点:QT5.10.1 的 mingw53_32 目录下的 lib 文件夹里存放着预编译的 Qt5Core.dll、Qt5Network.dll 等库文件,第三方 MQTT 源码包编译产物的运行时依赖就是这些文件。如果编译时用了错误版本的库,运行阶段会直接弹出"无法定位程序输入点"之类的错误,这种问题比编译报错更隐蔽。
2.2 第三方 MQTT 库的选择:qmqtt 与 emqx 的取舍
QT 平台上实现 MQTT 客户端主要有三条路线:官方 Qt MQTT 模块(仅商业授权部分版本可用)、qmqtt 开源库、emqx 的 qmqtt 分支。这份源码包采用的是 qmqtt 库的源码级移植,理由是它在 QT5.10.1 + minGW 环境下的兼容性最好。qmqtt 库提供了完整的 MQTT 3.1.1 协议实现,包括 CONNECT、PUBLISH、SUBSCRIBE、PINGREQ 等报文处理,而且源码包里的 mqtt 目录可以直接用 qmake 生成 Makefile 编译成静态库或动态库。
protocol 层和 client 层是分开的:qmqtt/src/mqtt/qmqtt_client.cpp负责会话管理、心跳、重连逻辑;qmqtt/src/mqtt/qmqtt_protocol.cpp负责报文编解码。你如果只是想快速跑通,直接用QT += mqtt是不行的,因为官方模块没有随开源版 QT5.10.1 一起发布。所以源码包里给了 qmqtt 的完整工程文件,这反而是好事——你可以看到协议栈内部实现,调起问题来不抓瞎。
我一般会建议优先用源码包自带的 qmqtt 编译产物,不要试图去替换成自己从网上拉的最新版。qmqtt 上游一直在更新,有人拉取了较新版本后发现 API 变了,原来的setHostName改成了setHost,原来是connectToHost的参数变了解释不同,一改就牵一发动全身。源码包里的版本是作者验证过的,接口稳定。
2.3 Qt Creator 编译器套件配置全过程
打开 Qt Creator,进入 Tools > Options > Kits,检查编译器列表里是否有 minGW 5.10.1 对应的 gcc。没有的话,需要手动添加:Compiler 页面点击 Add > MinGW,在 Compiler path 指向C:\Qt\Qt5.10.1\Tools\mingw530_32\bin\gcc.exe,注意这里 Tools 目录下的 minGW 才是独立工具链,和前面提到的 mingw53_32 目录里的运行库是配套的。C++ 编译器同理,指向 g++.exe。Debugger 页面选择 GDB,路径在bin\gdb.exe——一般会自动识别,如果没识别到就手动指过去。
Kit 页面点击 Add,Name 填写"Qt5.10.1 mingw5.10.1",Compiler 两个下拉框都选刚才新增的 minGW 5.10.1,Qt mkspec 选win32-g++,CMake 可以不配置。关键一步是 Qt version 页面,点击 Add 后选择qmake.exe路径,Qt Creator 会自动读取版本号显示 5.10.1。这些都完成后,才真正具备了编译 MQTT 源码包的环境基础。套件名字里的 minGW 5.10.1 和你 compiler 实际版本保持一致中间有偏差是很常见的,关键是 qmake 对应的库和编译器对应得上。
3. 移植 MQTT 源码到 QT 工程:编译顺序、pro 文件配置与路径解析
3.1 源码包的目录结构与编译顺序
拿到这份 QT5.10.1+MQTT+minGW5.10.1.zip 后,先解压到无中文无空格的路径,比如D:\workspace\qt_mqtt。完整的源码包结构是分层的:根目录下有mqtt_lib、mqtt_demo、tools三个文件夹。编译顺序不能乱,必须先编译库再做示例工程,因为 demo 依赖 mqtt_lib 生成的 dll 或 a 文件。很多人直接把 mqtt_demo 文件夹拖进 Qt Creator 点运行,结果找不到头文件或链接失败,就是跳过了第一步库编译。
先打开mqtt_lib文件夹下的mqtt.pro工程文件。用 Qt Creator 打开后,检查mqtt.pro里是否包含以下关键配置:
QT += network CONFIG += staticlib TARGET = qmqtt TEMPLATE = lib INCLUDEPATH += src/mqtt HEADERS += src/mqtt/qmqtt.h \ src/mqtt/qmqtt_client.h \ src/mqtt/qmqtt_message.h SOURCES += src/mqtt/qmqtt_client.cpp \ src/mqtt/qmqtt_message.cpp这里CONFIG += staticlib决定编译成静态库,编译产物是一个libqmqtt.a文件,静态库的好处是不需要在部署时额外带上 dll,直接在 demo 里链接进来,适合做产品交付。如果你更习惯动态库版本,可以把这行改成CONFIG += dll,生产环境下会输出qmqtt.dll和对应的 a 文件,demo 运行时需要保证 dll 在搜索路径下。
在 Qt Creator 左下角选择编译套件为上面建的 "Qt5.10.1 mingw5.10.1",点击 Build 按钮。正常情况下一两分钟就能完成,在mqtt_lib目录下会生成release或debug子目录,里面有libqmqtt.a文件。我实际编译时遇到过一个问题:直接点构建报错 "cannot find -lqmqtt",原因是 qmake 的 shadow build 机制把编译输出放到了另一个临时目录,导致链接时找不到库文件。解决办法是取消 shadow build:Projects 页面,Build Settings 里取消勾选 Shadow build,让编译产物直接生成在源码目录下。
3.2 集成到业务工程:pro 文件 LIBS 与 INCLUDEPATH 的做法
库编译通过后,开始把 MQTT 能力集成到自己的业务工程里。假设你自己的工程叫device_monitor.pro,需要做的是手工编辑 pro 文件,加入 qmqtt 库的路径和头文件路径。正确配置如下:
QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets INCLUDEPATH += D:/workspace/qt_mqtt/mqtt_lib/src/mqtt LIBS += -LD:/workspace/qt_mqtt/mqtt_lib/release -lqmqtt SOURCES += main.cpp \ mainwindow.cpp HEADERS += mainwindow.h FORMS += mainwindow.uiINCLUDEPATH 指向 qmqtt 源码的 mqtt 目录,因为头文件都在那里。LIBS 里-L后面必须是静态库或动态库所在的实际路径,-lqmqtt是小写形式,Qt 在 minGW 下会自动去匹配libqmqtt.a或libqmqtt.dll.a文件。注意这里有个坑:如果你的编译输出不是在 release 目录而是 debug 目录,那 LIBS 路径也必须跟着改成 debug。我见过有人把 debug 编译产物和 release 路径混着填,链接阶段报错 "undefined reference to" 一串符号找不到,原因就是 ABI 或库文件路径不匹配。
配置好 pro 文件后先做一次 qmake,确认没有语法错误,再构建整个工程。编译过程中如果看到qmqtt_client.h: No such file or directory,说明 INCLUDEPATH 没生效或路径写错了。如果看到cannot find -lqmqtt,说明 LIBS 路径不对,先检查 release 目录下是否真的有libqmqtt.a文件,没有的话回到库工程重新编译。
3.3 首次连通性验证:用公共 broker 测试收发
工程编译通过后,在 mainwindow.cpp 里写一个最精简的 MQTT 连接测试,确认整套环境是通的。代码逻辑如下:
#include "qmqtt_client.h" #include <QDebug> void MainWindow::testMqttConnection() { qmqtt::Client *client = new qmqtt::Client(QHostAddress("broker.example.com"), 1883, this); client->setClientId("qt_mqtt_test_001"); client->setUsername("test_user"); client->setPassword("test_pass"); QObject::connect(client, &qmqtt::Client::connected, [=]() { qDebug() << "MQTT Connected OK"; client->subscribe("test/topic", 0); client->publish("test/topic", "hello from qt5.10.1"); }); QObject::connect(client, &qmqtt::Client::received, [=](const QByteArray &msg) { qDebug() << "Received:" << msg; }); client->connectToHost(); }这段代码里qmqtt::Client是客户端类,构造函数第一个参数是 broker 地址,第二个是端口。setClientId设置设备唯一标识,在连接同一个 broker 时 ClientId 重复会导致前一个连接被踢掉,这在后面设备认证时会提到。subscribe三个参数分别是主题、QoS 等级、可选的回调。received信号在收到消息时触发,参数就是消息体。
提示:
connectToHost()是非阻塞的,连接结果通过connected信号判断。如果连接失败去查 broker 地址、端口、防火墙是否放行 1883 端口。
编译运行后,Qt Creator 的应用程序输出窗口会先打印 "MQTT Connected OK",然后收到自己发布的消息并打印 "Received: hello from qt5.10.1"。这一步跑通了,意味着库的移植和工程集成百分百成功,后面做业务对接可以安心。如果在这里连接超时,先检查防火墙——Windows 默认会拦截外网主动连接,需要在防火墙高级设置里添加入站规则放行 1883 端口,或者临时关闭防火墙验证问题。
4. 与物联网云平台对接:产品创建、三元组配置与数据上报
4.1 平台侧的产品与设备创建
把 MQTT 能力用到物联网云平台上,是这份资源最常见的实战场景。以某物联网基础服务平台(物联平台/云端接入服务)为例,整个对接流程分四步:创建产品、定义功能(属性/事件/服务)、添加设备、获取设备证书三元组。产品创建时推荐选择 MQTT 协议,数据格式选 JSON,这个平台支持 MQTT 3.1.1,与 qmqtt 库的实现完全兼容。
进入平台控制台后,创建产品时注意产品名称建议全英文,设备数量按实际业务需要考虑。产品创建完成后,在物模型(功能定义)页面添加属性,比如"温度"属性标识符Temperature,数据类型 float,读写类型读写。属性标识符在后续 payload 构造时必须完全一致,大小写错了平台会直接丢弃这条消息。我认识一个做设备接入的工程师,定义了Humidity属性,代码里写成humidity,结果上报的数据在控制台永远看不到更新,查了半天才发现是这个原因。
添加设备时选择刚创建的产品,设备名称填device_001这类唯一标识。添加成功后平台会生成设备证书,实际对接时只要拿到 ProductKey、DeviceName、DeviceSecret 这三个参数,它们就是设备的身份凭证。使用 qmqtt 连接平台时,ClientId 需要拼接为固定格式,Username 是 ProductKey 加 DeviceName 的组合,Password 是签名计算结果,拼接格式和签名算法在平台文档的设备接入章节有详细说明。
4.2 连接参数的构造与签名算法
设备连接平台不是简单的 broker 地址加端口,还需要做签名认证。连接参数一般这样组织:
- Broker 地址:
productKey.iot-as-mqtt.cn-shanghai.aliyuncs.com,具体地域不同,域名后半段有差异 - 端口:1883
- ClientId:
device_001|securemode=3,signmethod=hmacsha1,timestamp=789| - Username:
device_001&productKey - Password:用 HMAC-SHA1 算法计算的签名,密钥是 DeviceSecret
签名计算的原始字符串格式是clientIddevice_001deviceNamedevice_001productKeyproductKeytimestamp789,然后以 DeviceSecret 为密钥做 HMAC-SHA1 计算,结果转成十六进制字符串。这个顺序不能乱,上面那个字符串拼接顺序是平台要求的标准格式,少了一个字段或者顺序调整都会导致认证失败。怎么验证自己算的签名对不对?平台文档提供了签名计算工具,把参数填进去对比输。代码端我用的计算方式如下:
#include <QCryptographicHash> #include <QMessageAuthenticationCode> QString computeSign(const QString &clientId, const QString &deviceName, const QString &productKey, const QString &deviceSecret, const QString ×tamp) { QString content = QString("clientId%1deviceName%2productKey%3timestamp%4") .arg(clientId).arg(deviceName).arg(productKey).arg(timestamp); QByteArray key = deviceSecret.toUtf8(); QByteArray data = content.toUtf8(); QByteArray hash = QMessageAuthenticationCode::hash(data, key, QCryptographicHash::Sha1); return QString(hash.toHex()); }注意content字符串拼接的顺序里没有回车和空格,有的平台文档里展示的签名示例带了换行,那是显示格式问题。在代码里拼串时千万不要手动加任何空白字符,否则签名不通过。timestamp的生成用QDateTime::currentMSecsSinceEpoch()或平台允许的当前毫秒时间戳,这里注意很多示例代码用的是秒,但平台要求毫秒,我在这上面踩过一次坑,认证一直报"sign validate failed",排查到最后才发现是时间戳单位问题。
连接建立时把参数传给 qmqtt:
client->setClientId(clientId); client->setUsername(username); client->setPassword(password); client->connectToHost();整个连接流程和之前公共 broker 测试的唯一区别就是多了认证参数,qmqtt 内部会把这些参数编码进 CONNECT 报文,平台侧收到后做签名校验,通过则建立连接,不通过则断开并主动踢掉链路。调试阶段建议先打开 qmqtt 库的调试输出,能看到平台返回的 CONNACK 报文内容,报错码 5 表示认证失败,直接去核对签名计算。
4.3 属性上报的 JSON 结构封装
认证通过后最重要的事情就是把设备数据上报到平台。平台规定的属性上报 topic 格式有固定规范,QoS 必须设为 0 或 1,payload 是 JSON 字符串,里面要包含id、params、method三个字段。比如温度传感器上报一个 36.5 度的值:
void MainWindow::reportProperty(float temperatureValue) { QJsonObject paramObj; paramObj["Temperature"] = temperatureValue; QJsonObject payloadObj; payloadObj["id"] = "123456"; payloadObj["version"] = "1.0"; payloadObj["method"] = "thing.event.property.post"; payloadObj["params"] = paramObj; QJsonDocument doc(payloadObj); QByteArray payload = doc.toJson(QJsonDocument::Compact); client->publish(Topic_Broker_Post, payload, 0); }Topic_Broker_Post的值在平台上可以看到,格式类似/sys/{productKey}/{deviceName}/thing/event/property/post。注意 publish 的 QoS 选 0 还是 1,平台上默认推荐 QoS 0,因为属性上报频率高,丢失一帧数据影响不大;但如果做设备上下线记录这种关键事件,建议用 QoS 1 保证可达性,qmqtt 对 QoS 1 的 PUBACK 处理逻辑是完善的,不用自己实现确认响应。
上报成功后在平台控制台的设备详情页能看到属性值更新。如果上报率一直为零,用平台的日志服务查看上行消息分析,常见错误是 payload 里的属性标识符与物模型定义的不一致,或者温度值超出定义的量程范围。平台对非法 payload 会直接丢弃且不提醒设备端,这需要你通过日志服务反馈逐步排查。
5. 避坑指南:minGW 环境下的 MQTT 移植常见问题与排查记录
5.1 编译报错 undefined reference toqmqtt::Client::Client(...)
现象:自己的工程编译时链接报错,一堆 "undefined reference to qmqtt::Client::Client(QHostAddress, int, QObject*)" 之类信息。原因:链接器找不到 qmqtt 库的具体实现。可能是库文件没有编译成功,也可能是 LIBS 路径和编译套件版本不匹配,还可能是编译库工程时用的 kit 和编译自己工程时用的 kit 不一致,ABI 不同导致库内的符号对不上。解决:先确认mqtt_lib/release目录下存在libqmqtt.a,然后确认 LIBS 里的路径是绝对路径且大小写正确。在 Qt Creator 的构建输出里加上链接命令打印,看实际传给 g++ 的-L和-l参数是否和预期一致。最稳妥的方案是把 MQTT 库工程和自己的工程放在同一个项目中,用 SUBDIRS 模板统一编译,这样套件一定一致。
5.2 运行时报错 "cannot find -lqmqtt" 或加载库失败
现象:编译通过后运行程序,提示找不到 qmqtt.dll 或无法加载动态库。原因:如果库工程编译的是动态库版本,dll 没有拷贝到 exe 同目录下,或者没有加到 PATH;如果是静态库版本,几乎不会出现这种问题。解决:如果用的是 dll 版,把生成的 qmqtt.dll 手动拷贝到 exe 所在目录,或者把 dll 路径加进系统 PATH。也可以用 windeployqt 工具自动收集依赖。我实际操作倾向直接编译成静态库,省掉 dll 部署的麻烦,但静态库会使最终 exe 体积增大,这个取舍看产品需求。
5.3 MQTT 连接云平台提示 "clientId invalid" 或 "sign error"
现象:连接远程物联网平台时,CONNACK 返回错误码表示认证失败,平台日志显示签名校验不过。原因:ClientId 拼接格式有问题,最常见是签名原文拼接顺序错乱、 timestamp 用了秒而不是毫秒、或productKey和deviceName大小写写错。解决:严格按照平台文档里的例子构造 ClientId 和 Username 格式,不要自己调整顺序。写一个独立的签名校验工具函数,先不要连接,在本地算好签名后和平台的签名计算工具比对一下。确认一致后,再去建立连接,把问题定位留在签名环节之前。经验是:签名问题占这类接入失败的八成,时间戳问题占一半还多。
5.4 接收不到下发的消息:sub 和 topic 过滤器不一致导致
现象:设备可以上报数据,但平台下发的属性设置、服务调用指令设备收不到。原因:订阅的 topic 过滤器与平台实际下发消息的 topic 不匹配。平台下发消息的 topic 格式和上行完全不同,并且区分属性和服务。解决:在代码里同时订阅正确主题并监听 received 信号:
client->subscribe("/sys/productKey/deviceName/thing/service/property/set", 1); client->subscribe("/sys/productKey/deviceName/thing/service/invoke", 1);不要试图用#通配符订阅所有 topic,平台在权限控制上做了限制,不匹配的订阅会被拒绝。在调试时,先到平台日志服务里查看下行消息的 topic,再用同样的 topic 去订阅,基本一次就能对。
5.5 平台频繁踢掉连接:心跳保活参数需要调整
现象:前面一两次连接正常,隔几分钟后自动断开,平台侧显示设备离线,反复连接反复掉线。原因:qmqtt 默认的 keepalive 间隔是 60 秒或者更长,而平台的空闲超时时间更短,比如 30 秒,导致设备在超时后才发心跳,平台已经判定设备失联。解决:显式设置 keepalive 时间,qmqtt 的Client提供了setKeepAlive方法,改成 30 秒或更小。同时要确保程序没有假死,如果 UI 线程做了阻塞操作导致事件循环无法处理定时器,心跳也会发不出去。这里有个隐蔽的坑,在主线程里做阻塞性延时(比如QThread::sleep)会导致 PINGREQ 报文发不出去,平台必然断开该连接。正确做法是心跳交给 qmqtt 内部定时器处理,业务代码避免阻塞事件循环。
6. 进阶验证技巧:断线重连与 QoS 1 的消息可靠性验证
把基本收发跑通只是起点,生产环境里还要验证断线重连和数据不丢。qmqtt 客户端自带的自动重连机制属性是setAutoReconnect,默认关闭。你可以在 connected 信号里做一些业务初始化,同时在 disconnected 信号里打印原因码,排查异常断开的因素。一个典型配置如下:
client->setAutoReconnect(true); client->setAutoReconnectInterval(3000); QObject::connect(client, &qmqtt::Client::disconnected, [=]() { qDebug() << "Disconnected, reason:" << client->errorString(); });做断线重连测试,我这里有一个经验方法:启动程序连接成功后,直接拔网线,观察程序是否在 3 秒后自动重连成功;如果连接恢复但重连失败,大概率是客户端 ID 冲突或者平台会话保持策略的干扰。重新插上网线后,程序应该能在不重启的情况下恢复数据上报和指令接收。
接着做消息可靠性验证,把发布端的 QoS 升到 1,在 qmqtt 收到 PUBACK 时打印确认信息。qmqtt 对 QoS 1 的处理是自动完成的,你只需要在 publish 后等待确认即可,不需要自己实现配合确认逻辑。真机上验证消息不丢的方法是:在硬件设备上连续上报 1000 条数据,平台侧拉取数据判断是否有缺失。如果发现偶尔丢数据,检查是否因为网络延迟导致publish超时,qmqtt 内部的重发机制对超时消息会自动重发,前提是确认超时参数合理,默认值较长,但可以通过修改源码里的_willInterval或定时器间隔来适配实际网络状况。
再补一个消息到达率的额外验证路径:用自己写的模拟客户端订阅同一个 topic,然后用真实设备上报数据,观察模拟客户端是否全部收到,这个方法可以区分是平台丢消息还是设备侧发送不完整。实际测试后,如果发现是设备侧的问题,优先检查是publish报文太大被中间网关丢弃,还是 Qos 0 在非稳定网络下导致丢失。物联网场景建议核心数据走 QoS 1,周期性日志类数据走 QoS 0,性价比更高。
关于这份 QT5.10.1 + MQTT + minGW5.10.1 源码包的另外一个加分项是里面的 demo 工程把 connect、publish、subscribe、receive 四种操作的细节都写好了,能在 Qt Creator 里直接编译运行。完整移植过程下来,我的一个习惯是每次拿到源码包先把 pro 文件过一遍,确认 TARGET、LIBS、INCLUDEPATH 三处路径和编译套件匹配,再看信号槽连接是否冗余或缺失,最后再编译。从那以后我每次移植网络库到 QT 工程都强制走一遍这个流程,节省的排错时间非常可观。这份资源把从环境搭建到协议栈联调的最难几步都处理好了,剩下的是你业务逻辑的整合,希望帮到你。
本文还有配套的精品资源,点击获取