☰
Qt 5.6.1+MinGW 4.9.2移植MQTT与阿里云物联网平台接入
2026/10/27 10:35:46 网站建设 项目流程

简介:这份压缩包面向需要在 Windows 上基于 Qt 5.6.1 与 minGW 4.9.2 接入 MQTT、并对接阿里云物联网平台的开发者,提供一套可编译运行的 MQTT 客户端示例与配套库文件。包内共 28 个文件,以 20 个 .h 头文件、2 个 .a 静态库、2 个 .dll 动态库为主,另有 2 个 .cpp 源文件、1 个 .ui 界面文件和 1 个 .pro 工程文件,整体约 1.91MB,兼顾接口声明、库链接与示例工程搭建。内容围绕第三方 MQTT 库集成展开,可用于理解连接、发布、订阅等基本流程,以及设备标识与密钥鉴权、云端指令接收和数据上传等环节。已有 1701 人学习,适合物联网与嵌入式方向开发者对照移植,快速梳理 Qt 工程配置、库依赖和阿里云平台交互思路。

1. 版本先定死:Qt 5.6.1 + MinGW 4.9.2 移植 MQTT 为什么还值得折腾

前阵子又碰到一个典型现场:Qt Creator 里工程编译到一半,红色报错停在 MQTT 头文件第一行,报错的人坚持说库是坏的。看了一眼编译套件就明白了——Qt 5.15 配 MinGW 8.1,要链接的库却是别人用 QT5.6.1 + minGW4.9.2 编出来的 32 位版本。这种错位在 QT 移植 MQTT 协议这件事上太常见,尤其那些要把数据往阿里云物联网平台推的工控、网关类老工程:机器上还跑着 Win7,工程年龄比接手的人还长。

Qt 5.6.1 加 MinGW 4.9.2 是当年官方打包最完整的一套 32 位组合,编译器和 Qt 库版本一一对应,不挑机器,也不依赖新版运行库。装不好的后果不是编译不过那么简单,常见是链接过了启动闪退,或者连上平台几秒后被踢下线。下面按我这几年的装机顺序走:装 Qt、编 MQTT 库、接工程、上平台、验证。

2. 装 Qt 5.6.1 与 MinGW 4.9.2:安装器勾选和工具链落位

2.1 安装包选择与组件勾选

Qt 5.6.1 这个年代的安装器是整包下载,文件名形如qt-opensource-windows-x86-mingw492-5.6.1.exe,注意文件名里的mingw492不是装饰——它说明这个包里自带的编译器是 MinGW 4.9.2,和 Qt 库是同一批构建产物。很多人图省事从别的机器拷一个 MinGW 过来,版本差一点点,链接期就会冒出各种找不到符号的问题。

安装器跑起来之后的组件树,只勾这几项就够:

组件路径是否必选作用
Qt 5.6.1 → MinGW 4.9.2 32 bit必选Qt 核心库、网络模块、qmake
Qt 5.6.1 → Sources建议勾选调试时能跟进 Qt 源码
Tools → MinGW 4.9.2必选g++、gdb、mingw32-make
Tools → Qt Creator必选IDE 本体

MSVC 那一堆组件不要勾,勾了之后 Qt Creator 里会多出几个 Kit,配置的时候容易选错。安装路径我一般写C:\Qt\Qt5.6.1,路径里绝对不能有空格和中文——MinGW 的 make 对路径处理很粗糙,Program Files这种带空格的目录能让编译在某个莫名其妙的步骤失败,而且报错信息根本不指向路径问题,这是血泪经验。

装完之后磁盘上会长出两个关键目录:

  • C:\Qt\Qt5.6.1\5.6.1\mingw49_32\bin—— 放 qmake、Qt5Core.dll 这些
  • C:\Qt\Qt5.6.1\Tools\mingw492_32\bin—— 放 g++、gcc、mingw32-make、gdb

Qt 库和编译器分属这两个目录,后面所有配置错误基本都是这两个目录没对齐导致的。

2.2 环境变量和 Kit 的对齐

装完先在系统 PATH 里追加这两个bin目录,Qt 的bin放前面。之所以要动 PATH,是因为 qmqtt 这类第三方库的.pro文件里只写了qmake,你在命令行直接敲的时候走的就是 PATH 里第一个。

Qt Creator 里打开「工具 → 选项 → 构建和运行」,检查 Kit 页签。一个正确的 Kit 应该有这几项,且全部指向同一个安装根目录:

# Kit 里四个路径应该长这样,前缀必须一致 编译器 C++ : C:\Qt\Qt5.6.1\Tools\mingw492_32\bin\g++.exe 调试器 : C:\Qt\Qt5.6.1\Tools\mingw492_32\bin\gdb.exe qmake 位置 : C:\Qt\Qt5.6.1\5.6.1\mingw49_32\bin\qmake.exe Qt 版本 : 5.6.1 MinGW 4.9.2 32bit

常见的错法是编译器用了 Qt5.6.1 目录里的,qmake 却指向了另一个 Qt 版本的bin。这种 Kit 能创建、能新建工程,编小 demo 也没事,但一链接第三方库就炸。判断方法很简单:看 qmake 报告的 Qt 版本和编译器位数,两个必须同时是 32 位。

2.3 装完先验三条命令

在开始编 MQTT 库之前,先开一个全新的命令行窗口(不是 Qt Creator 里的,是系统的 cmd),跑这三条:

# 1. 看 qmake 报告的 Qt 版本和编译器 qmake -v # 2. 看 g++ 版本,应该报 4.9.2 g++ --version # 3. 看目标平台,这条最关键 g++ -dumpmachine

第一条正常输出是QMake version 3.0加Using Qt version 5.6.1,并且下面会带一行using toolchain from ...\Tools\mingw492_32。第三条的预期输出是i686-w64-mingw32,i686表示 32 位,如果输出的是x86_64说明 PATH 里还有一个 64 位 MinGW 排在前面,必须把它挪走或者删掉。

提示:如果g++ -dumpmachine输出 64 位,而 Qt 是 32 位,后面编出来的库一定是废的,只是编译器不会当场告诉你。这一条验证别省。

这三条对得上,工具链这一层就算落地了。对不上就别往下走,先解决路径问题——我见过太多人跳过这步,然后在编库阶段花两天时间怀疑人生。

3. 编 qmqtt:用 MinGW 4.9.2 产出能链接的 MQTT 库

Qt 5.6.1 本身不带 MQTT 模块,官方把 QtMqtt 做成正式模块是很久以后的事。所以在 5.6.1 上做 MQTT,通行做法是拿 qmqtt 这类第三方库自己编一份,产出的库文件跟你的 Qt 版本、编译器位数严格绑定。

3.1 源码目录和 .pro 结构

qmqtt 的源码树大致是:

qmqtt/ ├── qmqtt.pro # 顶层 subdirs 工程 ├── src/ │ ├── qmqtt.pro # 真正编库的工程 │ ├── qmqtt.h # 对外头文件 │ ├── qmqtt_client.h │ └── ... └── examples/

在动手之前先搭一个干净的第三方目录,我一般放在工程同级:

D:\dev\qmqtt-build\ ├── qmqtt\ # 源码解压到这里 └── output\ # 编译产物往这里归 ├── include\ ├── lib\ └── bin\

之所以要把产物单独归一个目录,是因为 qmqtt 默认把 dll 和 .a 都扔在src/release或src/debug下,debug 和 release 同名但内容不兼容,来回切换工程时极容易拷错。

3.2 用 Qt 5.6.1 的 qmake 生成 Makefile

不要直接敲qmake,用绝对路径调用,这是我这几年养成的强制习惯——PATH 里同时存在多个 Qt 版本时,天知道第一个 qmake 是谁:

# 进入源码目录 cd /d D:\dev\qmqtt-build\qmqtt # 1. 用 5.6.1 的 qmake 生成 Makefile,指定 release 配置 C:\Qt\Qt5.6.1\5.6.1\mingw49_32\bin\qmake.exe qmqtt.pro CONFIG+=release # 2. 编译,-j4 用四个核,老机器改 -j2 mingw32-make -j4 # 3. 安装到指定前缀(如果 .pro 里配了 target.path) mingw32-make install

第一条命令里的CONFIG+=release是给 qmake 传的变量,它会覆盖.pro里的默认配置,让产物走 release 分支。这一步生成的 Makefile 是给 mingw32-make 用的,里面已经写死了 g++ 的路径和 Qt 的头文件搜索路径,所以后面不能换编译器。

mingw32-make -j4里-j是并行任务数。qmqtt 源码量不大,四核十几秒完事。如果出现cc1plus.exe: out of memory之类的报错,把-j4去掉单线程跑,或者换机器——这是 MinGW 4.9.2 在内存紧张时的老毛病,不是代码问题。

第三条install只有在顶层.pro写了target.path和INSTALLS时才有效。qmqtt 不同版本的写法有差异,如果没有 install 目标,直接手动拷产物也行,下一节说。

3.3 产物归位:头文件、导入库和 dll 的分工

MinGW 下编译一个动态库,产物是两样东西:

产物典型路径用途
libqmqtt.asrc/release/libqmqtt.a链接期用的导入库,-lqmqtt找的就是它
qmqtt.dllsrc/release/qmqtt.dll运行期必须随 exe 一起分发
qmqtt.h等src/*.h编译期头文件,INCLUDEPATH指向它

如果在.pro里加了CONFIG += staticlib,就没有 dll,只有一个大号的libqmqtt.a,链接进 exe 之后分发省事,但体积会涨,而且以后换库必须整个重编。我一般调试期用动态库方便替换,出包前再考虑静态。

归位操作:

# 复制头文件(保持目录结构,qmqtt 内部头文件互相引用) xcopy D:\dev\qmqtt-build\qmqtt\src\*.h D:\dev\qmqtt-build\output\include\ /Y # 复制导入库和 dll copy D:\dev\qmqtt-build\qmqtt\src\release\libqmqtt.a D:\dev\qmqtt-build\output\lib\ copy D:\dev\qmqtt-build\qmqtt\src\release\qmqtt.dll D:\dev\qmqtt-build\output\bin\

debug 版要单独编一遍:把上面 qmake 那步的CONFIG+=release换成CONFIG+=debug,产物名一般是libqmqttd.a和qmqttd.dll,单独放lib\debug和bin\debug。debug 库和 release 库的 C++ 运行时不同,混着链接会在运行期抽出各种内存相关的异常,这类问题用调试器都不好定位。

注意:拷头文件时别只拷qmqtt.h。qmqtt 的公开头文件之间是互相 include 的,只拷一个,用户工程编译时会报找不到qmqtt_client.h之类的错误。

4. 接进 Qt 工程:.pro 配置与阿里云物联网平台连接参数

库编好了,接下来是把它接进你自己的工程,然后把连接参数按平台要求拼对。这一段是翻车最集中的地方,因为参数错一个字符,平台不会告诉你哪个错了,只回一个连接失败。

4.1 .pro 里三行关键配置

把前面归位的output目录拷到工程目录下,或者用相对路径指过去。.pro里加这三段:

QT += core network CONFIG += c++11 # 头文件搜索路径,指向归位目录 INCLUDEPATH += $$PWD/third_party/qmqtt/include DEPENDPATH += $$PWD/third_party/qmqtt/include # 库搜索路径 + 库名,MinGW 下 -lqmqtt 会自动找 libqmqtt.a LIBS += -L$$PWD/third_party/qmqtt/lib -lqmqtt

$$PWD是 qmake 的内置变量,表示当前.pro文件所在目录,用它拼路径可以保证工程整体拷贝到别的机器上还能编。QT += network必须加,qmqtt 内部依赖QTcpSocket,漏了这一行会在链接期报一堆QTcpSocket未定义。

-L后面跟目录,-l后面跟库名,注意-lqmqtt中间不能有空格,也不能写成-llibqmqtt——MinGW 会自动补lib前缀和.a后缀。如果库是 debug 版,这里要改成-lqmqttd。

写完.pro之后,Qt Creator 里必须执行一次「构建 → 执行 qmake」,否则改动不生效。很多人改了.pro直接点运行,然后抱怨改了没用,就是这个原因。

4.2 三元组与 ClientID 的拼法

阿里云物联网平台用「三元组」做设备身份:ProductKey、DeviceName、DeviceSecret。前两个是明文,第三个只用来算签名,绝对不能出现在报文里。连接时填的三个字段是 ClientID、Username、Password,每个都有固定拼法:

字段拼法说明
ClientID<clientId>|securemode=3,signmethod=hmacsha1,timestamp=<毫秒时间戳>|securemode=3是 TCP 直连,=2是 TLS
Username<deviceName>&<productKey>中间是&,顺序别反
Passwordhmacsha1(待签名串, deviceSecret)待签名串见下

待签名串的构造顺序是固定的:clientId+deviceName+productKey+timestamp,四个值按这个顺序直接拼接,中间不加任何分隔符,然后对这个字符串用 DeviceSecret 做 HMAC-SHA1,结果转成十六进制小写字符串。整个过程中最容易错的是顺序和大小写,签名是十六进制小写,别用toUpper()。

timestamp那一段虽然很多示例里写了个固定值,但实际服务器会做时效校验,建议用当前毫秒时间戳。Qt 5.6 里取时间戳要注意QDateTime::currentMSecsSinceEpoch()返回的是 qint64,拼字符串时用QString::number(ts)。

4.3 一段最小可跑的连接与订阅代码

下面这段是能直接编过的最小骨架,用 qmqtt 的QMQTT::Client:

#include <QCoreApplication> #include <QHostAddress> #include <QDebug> #include "qmqtt.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // 1. 建客户端,先给地址和端口,1883 是 TCP 直连端口 QMQTT::Client client(QHostAddress("<productKey>.iot-as-mqtt.<region>.aliyuncs.com"), 1883); // 2. 三个身份字段,按 4.2 的拼法填 client.setClientId("<clientId>|securemode=3,signmethod=hmacsha1,timestamp=2524608000000|"); client.setUsername("<deviceName>&<productKey>"); client.setPassword("<hmacsha1 算出来的十六进制小写串>"); // 3. 心跳 60 秒,平台对这个值有上限要求,别乱写大 client.setKeepAlive(60); // 4. 先连信号再发起连接,顺序反了会漏掉第一帧 QObject::connect(&client, &QMQTT::Client::connected, [&client]() { qDebug() << "connected"; client.subscribe("/<productKey>/<deviceName>/user/get", 0); }); QObject::connect(&client, &QMQTT::Client::received, [](const QMQTT::Message &msg) { qDebug() << "topic:" << msg.topic() << "payload:" << msg.payload(); }); QObject::connect(&client, &QMQTT::Client::error, [](const QMQTT::Client::Error err) { qDebug() << "mqtt error code:" << err; }); client.connectToHost(); return app.exec(); }

逻辑上分四段:构造客户端、填身份、设心跳、挂信号。第三段setKeepAlive(60)的单位是秒,平台侧对心跳间隔有允许范围,写太大不会立刻报错,但网络抖动时会被服务端判定超时踢掉。

第四段的connect必须写在connectToHost()之前,因为连接是异步的,如果先发起再挂信号,connected信号可能在两行之间就发出来了,订阅逻辑永远不执行。这个坑很隐蔽,表现是「偶尔能收到消息,偶尔收不到」,跟网络状况毫无关系。

subscribe的第二个参数是 QoS,0 表示最多一次,工控场景里上报类数据用 0 够用,控制指令这类不能丢的建议提到 1。回调里不要做耗时操作,qmqtt 的收包和回调在同一个线程,阻塞回调会拖慢心跳。

4.4 TLS 端口与 OpenSSL 运行库

如果平台要求走加密端口,把端口从 1883 换成 8883,securemode从 3 改成 2。但这一步在 Qt 5.6.1 上有个前提:QSslSocket必须真的支持 SSL。

Qt 5.6 时代 Windows 版不内置 OpenSSL,需要在 exe 同级目录放 32 位的libeay32.dll和ssleay32.dll,版本对应 OpenSSL 1.0.x 系列。放错了版本,或者放了个 64 位的 dll,程序不会报错,只会静默地连不上,这是最典型的黑匣子。

验证方法是在main开头加一行:

// 必须在 QCoreApplication 构造之后调用 qDebug() << "ssl support:" << QSslSocket::supportsSsl() << QSslSocket::sslLibraryVersionString();

第一个输出是false就说明 dll 没放对位置或缺依赖,第二个输出能告诉你实际加载到的 OpenSSL 版本号。先把它跑成true,再去连 8883,否则排查方向会全错。

5. 踩坑排查:编译、链接、握手三类翻车现场

5.1 链接报 undefined reference to QMQTT::Client

现象:工程编译阶段全绿,链接阶段刷一屏undefined reference to 'QMQTT::Client::Client(...)'。

原因基本是两个。一是.pro里-l后面的库名写错,或者库目录根本没指对,qmake 没报错是因为-L指定的目录存在,但里面没有匹配的.a。二是 32 位和 64 位混用,exe 是 32 位的,库是 64 位编的,链接器读都读不了。

解决:先看构建目录下的Makefile.Release,搜LIBS那一行,确认里面确实有-lqmqtt和你写的路径。再用g++ -dumpmachine确认编译器位数,用objdump -f libqmqtt.a看库的架构是pei-i386还是pei-x86-64,两边必须都是 i386。

5.2 双击 exe 闪退,或提示缺少 libgcc_s_dw2-1.dll

现象:在 Qt Creator 里运行一切正常,把 exe 拷到别的目录或者别的机器上,双击没反应,或者弹窗说找不到某个 dll。

原因:Qt Creator 运行时会把 MinGW 的bin和 Qt 的bin加进 PATH,所以能跑。脱离 IDE 就没有这个 PATH 了,缺的是libgcc_s_dw2-1.dll、libstdc++-6.dll、libwinpthread-1.dll这几个 MinGW 运行库,以及Qt5Core.dll、Qt5Network.dll,还有自己编的qmqtt.dll。

解决:出包前用 Qt 自带的部署工具跑一遍:

# 在 exe 所在目录执行,它会自动把依赖的 Qt dll 拷过来 C:\Qt\Qt5.6.1\5.6.1\mingw49_32\bin\windeployqt.exe --release your_app.exe

它不会拷 MinGW 运行库和qmqtt.dll,这两个要手动从Tools\mingw492_32\bin和output\bin拷到 exe 同级。我一般会把这几步写成一个deploy.bat,每次出包都跑。

5.3 连上之后立刻断开,错误码 5(not authorized)

现象:connected信号没触发,或者触发了但几十毫秒后error收到 5,服务端直接断开。

原因:身份字段拼错。按出现频率排序是——Password 签名用了大写十六进制、待签名串四个值的顺序不对、Username 里deviceName和productKey顺序颠倒、ClientID 里securemode与端口不匹配(1883 却写了securemode=2)。还有一种情况是 DeviceSecret 里有特殊字符,被误当成了别的编码。

解决:把待签名串和签名结果都qDebug打出来,跟平台控制台里设备详情页的「一键复制连接参数」逐字符比对。比对时注意时间戳不同会导致签名不同,所以要拿同一个时间戳去算。这个错我吃过一次,最后发现是拼串时多了一个空格,花了两个小时。

5.4 qmake 跑出来用的是别的 Qt 版本

现象:编 qmqtt 时一切正常,编自己工程时头文件报错,说某个 Qt 类型不存在,但那个类型明明是 5.6 就有的。

原因:PATH 里排在前面的 qmake 不属于 5.6.1。这个错误不会在编库阶段暴露,因为库的.pro简单,两边版本都能过,等到自己工程用了新版本没删掉的 API 才炸。

解决:任何时候都用绝对路径调用 qmake,不要依赖 PATH。可以在 Qt Creator 的「构建步骤 → qmake」里把「qmake 构建步骤」的路径显式指定成C:\Qt\Qt5.6.1\5.6.1\mingw49_32\bin\qmake.exe,并且在每次「执行 qmake」之后,看一眼「概要」里显示的 Qt 版本号。

5.5 收到消息但中文全是乱码

现象:平台侧下发的 JSON 里带中文,msg.payload()打出来是一串问号或者乱码。

原因:qmqtt 的Message::payload()返回的是QByteArray,直接qDebug()打字节数组会按本地编码解释,跟 UTF-8 对不上。不是收错了,是显示错了。

解决:取值的地方统一用QString::fromUtf8(msg.payload()),发送时用payload.toUtf8()。这个不算大坑,但排查时容易往协议方向想,绕远路。

6. 装完怎么验证:从本机回环到平台侧上下行全链路

装完整套环境之后,别急着往业务代码里塞。我固定跑三步验证,任何一步不过都不往下走。

步骤验证内容通过标准
第一步:本机回环客户端能否正常连、发、收同一进程内发布一条,received信号能收到
第二步:平台在线身份参数是否正确控制台设备状态变「在线」,且不掉线
第三步:双向链路上行与下行都通平台下发指令能被客户端收到,客户端上报能进平台消息日志

第一步用一段极短的代码就能做,把subscribe和publish写在一起:

// 连上之后先订阅,再往同一个 topic 发一条,验证收发闭环 QObject::connect(&client, &QMQTT::Client::connected, [&client]() { client.subscribe("/test/echo", 0); QMQTT::Message msg(1, "/test/echo", QByteArray("ping")); client.publish(msg); });

这一步能过,说明库链接、信号槽、线程模型都没问题,把变量收缩到了网络和身份参数上。过不了的话问题一定在本地,跟平台无关。

第二步看平台控制台的设备状态,重点不是「在线」两个字,而是能不能稳定在线超过一分钟。有些参数错误的表现是先上线再秒掉,短时间内刷新看不出来。我会把客户端开着,去泡杯茶回来再看一眼状态,还在线才算过。

第三步最容易被忽略:客户端上报的数据,要能在平台的消息日志里查到;平台下发的内容,要能在客户端日志里打出来。两个方向都验证过,才算真正跑通。只验证上行的话,订阅权限、topic 拼写这类错误会一直潜伏到上线。

还有个细节值得单独跑一遍:把程序最小化挂在那里两个小时,看心跳是否稳定。setKeepAlive设的 60 秒如果跟平台侧的限制冲突,短时间测不出来,长时间挂机才会暴露出规律性掉线。

从那以后我每次在一台新机器上装这套 Qt 5.6.1 加 MinGW 4.9.2 的环境,都会强制先把qmake -v、g++ -dumpmachine、QSslSocket::supportsSsl()这三条跑一遍并截图存档,再开始动业务代码。这三条对上了,后面 90% 的怪问题都不会发生;对不上还硬往下走,最后排查的时间够重装三遍系统。希望这套流程能帮你少走点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询