简介:面向需要在VS2019下使用MQTT C++客户端的开发者,paho.mqtt.cpp库的VS2019编译成品包源自配套博文教程,可直接对照使用。paho.mqtt.cpp是Eclipse Paho官方的MQTT C++客户端库,支持同步/异步API,常用于物联网设备接入、消息推送等场景,体系结构清晰。压缩包共含550个文件,包括44个h头文件、31个cpp源文件,以及编译生成的dll、lib库,还包含vcxproj工程文件、CMake配置、pdb调试符号、exe可执行程序等,整体约64.21MB;不同目录分别存放源码、构建脚本与产物,便于按需检索。目前已有2976人学习下载,作为配套教程的成品工程,可省去手工配置第三方依赖、编译选项和链接参数的繁琐过程。下载后既能查看完整源代码和工程组织方式,也能直接引用编译好的lib/dll快速集成到自己的MQTT应用中,适合有一定C++基础、希望加快开发进度的物联网开发者。
1. 拿到现成的 paho.mqtt.cpp 库:VS2019 编译版到底省了多少事
做 Windows 桌面端 MQTT 接入的 C++ 开发者,大概率都经历过这样的场景:项目工期压着,消息队列模块还没影,翻遍 NuGet 找不到官方 paho.mqtt.cpp 的现成包,GitHub 上 Release 只有源码,自己配 CMake、翻 OpenSSL、调 vcpkg,一搞就是两个晚上。这份 VS2019 编译完成的 paho.mqtt.cpp 库,就是拿来直接解决这个痛点的——它把paho.mqtt.c和paho.mqtt.cpp两层依赖一起编好了,头文件、lib、dll 按 VS2019 的目录结构整理完毕,你在 Visual Studio 2019 里建好工程、把包含目录和库目录指过去、链接器加上依赖项,就能开始写mqtt::client的发布订阅代码。
适合谁用:主力开发环境是 VS2019、不想折腾 vcpkg 和 CMake、只需要把 MQTT 客户端嵌进现有工程的 Windows C++ 开发者。这篇文章就把「怎么用起来」和「常见的坑」一次讲透,从配置步骤到运行时报错逐个过一遍。
2. 先搞清楚这份库的组成:头文件、lib 与 dll 的分工
2.1 库包的基本目录结构和必要文件
拿到压缩包解压后,典型的目录布局包含include、lib、bin(或dll)三层,其中include下应有mqtt目录,里面是 paho.mqtt.cpp 的头文件,还有mqtt目录里的client.h、message.h、topic.h这些核心接口;同时因为 paho.mqtt.cpp 对 C 库有依赖,头文件里会#include <mqtt/../mqtt/xxx.h>之类相对路径引用到 paho.mqtt.c 的头文件,所以include里一般还会放MQTTClient.h、MQTTProperties.h、MQTTReasonCodes.h等 C 库头文件。这两个缺一不可,少了哪边编译期直接报找不到头文件。
lib目录下通常区分Debug和Release两个子目录,每个目录里都有paho-mqtt3a.lib(异步 C 客户端)、paho-mqttpp3.lib(C++ 封装库)、有时还有paho-mqtt3as.lib(带 SSL 的异步客户端库)。bin目录对应存放运行时 DLL,比如paho-mqtt3a.dll、paho-mqttpp3.dll和paho-mqtt3as.dll。
这层结构决定了你在 VS2019 里要做的事:把 include 目录告诉编译器,把 lib 目录告诉链接器,把 DLL 拷贝到 exe 输出目录。如果这份库包还带了samples或example目录,建议先打开里面的 MQTT 发布订阅示例工程,确认编译链接跑通后再往自己的业务代码里迁。
2.2 为什么是这三个库文件:paho-mqtt3a 与 paho-mqttpp3 的关系
paho.mqtt.cpp 的架构是套了两层:底层是 paho.mqtt.c 这个 C 库,负责和 broker 的 TCP 连接、MQTT 协议报文编解码、心跳保活这些脏活;上层是 paho.mqtt.cpp 的 C++ 封装,把 C 的回调函数改造成mqtt::callback、mqtt::client这样的面向对象接口。你在代码里#include <mqtt/client.h>、调用mqtt::create_options时,用的是 C++ 这套 API,但链接时必须把 C 库的 lib 也加上,否则符号解析不完整。
// 异步客户端(paho-mqtt3a.lib)是底层依赖 // C++ 封装库(paho-mqttpp3.lib)依赖前者 // 如果你要用 TLS 加密连接,还需要链接 paho-mqtt3as.lib #pragma comment(lib, "paho-mqtt3a.lib") #pragma comment(lib, "paho-mqttpp3.lib")这段#pragma comment写在stdafx.h或pch.h里是偷懒的连法,VS2019 会在链接阶段自动带上这些库。但要注意:链接顺序有讲究,C++ 库必须在前、C 库在后,有些场景下反了会报unresolved external symbol,尤其是用了 TLS 版本时。更多时候我建议直接在项目属性的「链接器 → 输入 → 附加依赖项」里手动填,填的顺序同样保持paho-mqttpp3.lib在前、paho-mqtt3a.lib在后。
2.3 要不要区分 Debug 与 Release、x86 与 x64
这份库如果是用 VS2019 默认配置编译的,大概率包含 Win32(x86)和 x64 两套产物,也包含 Debug 和 Release 两套。但有个关键点:paho.mqtt.c 的 C 库编译时默认用的是/MD运行时库,也就是动态链接到vcruntime140.dll和msvcp140.dll;而你的 VS2019 工程如果设置的是/MT(静态运行时),链接时可能遇到LNK2038 mismatch detected for 'RuntimeLibrary'。
// 项目属性 -> C/C++ -> 代码生成 -> 运行库 // Debug 对应 /MDd // Release 对应 /MD // 如果不匹配,编译期报错形如: // error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MTd_StaticRelease' doesn't match value 'MDd_DynamicDebug'所以拿到库后第一件事:确认你的工程「配置管理器」里当前是 Debug 还是 Release、Win32 还是 x64,然后去库包里对应子目录取 lib。我一般直接把四个组合全部配置好——Debug/Win32、Debug/x64、Release/Win32、Release/x64——属性表分别指到对应目录,避免切配置后链接器找不到库。
3. 在 VS2019 里配好工程:从属性表到第一个发布订阅程序
3.1 配置包含目录、库目录和附加依赖项
这一节直接按可抄作业的步骤来。假设你的工程已经创建好了(控制台应用或桌面应用都行),打开「项目 → 属性 → VC++ 目录」,分别设置:
- 包含目录:
$(SolutionDir)3rdparty\paho.mqtt.cpp\include - 库目录:
$(SolutionDir)3rdparty\paho.mqtt.cpp\lib\$(Platform)\$(Configuration)
这里用$(Platform)和$(Configuration)宏是推荐做法,切换 Win32/x64、Debug/Release 自动找对应目录,不用手改。目录解压到哪就改成哪,但建议不要把库直接放 C 盘根目录或带中文的路径下,paho 的头文件包含逻辑没问题,但 VS2019 的 IntelliSense 对超长路径偶尔抽风。
然后点「链接器 → 输入 → 附加依赖项」,加上:
paho-mqttpp3.lib paho-mqtt3a.lib如果你要连 TLS 端口(8883),把paho-mqtt3as.lib也加上,同时保证paho-mqtt3a.lib不删——因为paho-mqtt3as.lib是 SSL 版异步库,C++ 封装层的 lib 同时依赖普通版和 SSL 版。链接顺序保持上面这个次序。
3.2 配置完成后跑通一个最小的发布订阅示例
配好属性后,先写一个最小的 MQTT 发布程序验证环境,不要一上来就连业务代码。下面这段是 paho.mqtt.cpp 里最典型的同步客户端发布代码:
#include <mqtt/client.h> #include <iostream> #include <string> int main() { // 1. 指定 broker 地址、客户端标识 std::string broker = "tcp://127.0.0.1:1883"; std::string clientId = "vs2019_paho_pub"; // 2. 创建客户端,用同步接口 mqtt::client client(broker, clientId); // 3. 连接 broker,设置连接超时 5 秒 mqtt::connect_options connOpts; connOpts.set_keep_alive_interval(20); connOpts.set_clean_session(true); client.connect(connOpts); // 4. 构造消息并发布到 test/topic std::string payload = "hello from vs2019 paho"; mqtt::message_ptr pubMsg = mqtt::make_message("test/topic", payload); pubMsg->set_qos(1); client.publish(pubMsg); // 5. 断开 client.disconnect(); std::cout << "published OK" << std::endl; return 0; }这段代码里要留意的有三个参数:broker地址如果连接本地调试用tcp://127.0.0.1:1883,连远程则替换成tcp://ip:port;mqtt::make_message的第一个参数是 topic、第二个是消息体字符串,payload 也可以是二进制数据,用mqtt::buffer包装std::vector<uint8_t>;set_qos(1)表示至少送达一次,broker 收到会回 PUBACK,如果设成 2 会有 PUBREC/PUBCOMP 两轮握手,吞吐会下降但可靠性更高。
3.3 运行前必须处理的 DLL 拷贝问题
编译能过不代表运行能过。paho 这套库是动态链接的,exe 启动时会去加载paho-mqttpp3.dll、paho-mqtt3a.dll(如果用 TLS 还有paho-mqtt3as.dll),Windows 的 DLL 搜索顺序是:exe 所在目录优先于系统 PATH。你如果直接把 DLL 留在库包的bin目录里不拷贝,运行时会报0xc0000135找不到 DLL。
常见做法是在项目属性里加一个生成后事件命令:
xcopy /Y "$(SolutionDir)3rdparty\paho.mqtt.cpp\bin\$(Platform)\$(Configuration)\*.dll" "$(OutDir)"这段是拷贝当前配置对应的 DLL 到输出目录,$(OutDir)默认是x64\Debug\或Debug\。如果库包的 DLL 目录没有区分$(Platform)\$(Configuration),而是全部平铺在一个bin下,那直接写xcopy /Y "...bin\*.dll" "$(OutDir)",注意拷完起一次程序确认 DLL 版本没拿错——Release 配置下不小心拷了 Debug 版 DLL,运行时会莫名崩溃,没有明确报错。
4. 从编译到运行最常见的坑:链接错、跑不动、连不上
4.1 链接期报 unresolved external symbol
现象:编译通过,链接时报LNK2019 unresolved external symbol "public: void __cdecl mqtt::client::connect(...)"或者一堆mqtt::string_convert相关的符号找不到。
原因:最常见的有三种。第一种是附加依赖项没加全,只加了paho-mqttpp3.lib忘了加paho-mqtt3a.lib;第二种是加了但链接顺序反了;第三种是库目录指向了和当前工程位数不匹配的 lib,比如 x64 工程配了 Win32 的 lib 目录,链接器在 32 位库里找不到 64 位符号。
解决:先把附加依赖项按「pp3 → 3a → 3as」顺序填好;确认「VC++ 目录 → 库目录」里的$(Platform)宏生效,手动写死路径时务必看平台;如果用了#pragma comment(lib)和属性表双写,删除一种方式,别两处都加。
4.2 运行时崩溃在 connect,报 0xC0000005
现象:程序启动后没几秒崩溃,断点定位在client.connect()调用内部,调用栈指向 paho 库内部的内存操作。
原因:大概率是 Debug/Release 不匹配。库包里的 Debug 版 lib/dll 是用调试运行时(/MDd)编的,你的工程是 Release(/MD),两边对std::string内部布局和堆管理器的实现不同,字符串在库边界传递时直接踩内存。还有一种可能是你拷的 DLL 是 Release 版,但工程是 Debug 版,同样崩。
解决:先确认「运行库」设置是/MD还是/MDd,和库包的子目录对应。强烈建议 Debug 工程 Debug 库、Release 工程 Release 库,不要混用。把 DLL 也按$(Configuration)分开拷贝,不要一个bin里拿到底。如果实在分不清库包里哪个是 Debug 哪个是 Release,用 Dependencies.exe(或旧版 Dependency Walker)打开 DLL 看它依赖的是VCRUNTIME140D.dll还是VCRUNTIME140.dll,带 D 的就是 Debug。
4.3 connect 报返回码 -1 或 ECONNREFUSED
现象:client.connect()抛异常,或者返回-1,错误信息里能看到Connection refused。
原因:broker 地址不对、端口没开、防火墙拦了。最常见的是本机根本没起 broker,或者 broker 监听了 1883 但绑定了 127.0.0.1,你用局域网 IP 去连当然拒绝;还有一种情况是 broker 开启了 TLS 但你的连接串写的还是tcp://,协议不匹配直接拒连。
解决:本地测试先确认 broker 起来了,Windows 下用netstat -ano | findstr 1883看端口监听状态。连远程 broker 时 telnet 一下telnet ip 1883,通的话再排查代码;不通查防火墙入站规则。用 TLS 就把连接串改成ssl://ip:8883,并且保证库是带 SSL 的版本(链接了paho-mqtt3as.lib),否则会报MQTTClient_connect: TLS not supported。
4.4 消息发布成功但订阅端收不到
现象:publish 正常返回,但另一个订阅客户端就是收不到数据,broker 端日志也没看到消息转发记录。
原因:多半是 topic 不匹配,或者 QoS 为 0 且正好赶上网络波动丢包。再有一种情况:订阅端用的是mqtt::topic通配符test/#,而发布端 topic 写成了test/topic/extra,这没问题;但如果发布端 topic 是test/topic,订阅端写的是test/+,也能收到。真正容易忽略的是订阅端没来得及完成 SUBSCRIBE 握手就发了消息——有些示例代码把subscribe和publish写在同一个线程里,subscribe是异步生效的,要等回调确认后才保险。
解决:调 QoS 到 1 先排除丢包;确认 broker 管理界面里能看到订阅关系;自己在发布端也开一个订阅接收自己发的消息做回环验证。用mqtt::client的set_callback注册message_arrived回调,收到消息打日志,比人眼判断可靠得多。
5. 进阶用法:异步回调与持久会话的正确打开方式
5.1 改用 consume_message 实现订阅消息处理
同步接口适合快速验证,但生产级的代码我一般用异步模式:把mqtt::client配成异步回调,注册callback子类,在message_arrived里处理消息,避免主线程阻塞在等待上。
#include <mqtt/client.h> #include <iostream> class user_callback : public mqtt::callback { public: void message_arrived(mqtt::const_message_ptr msg) override { std::cout << "topic: " << msg->get_topic() << ", payload: " << msg->to_string() << std::endl; } void connected(const std::string& cause) override { std::cout << "connected: " << cause << std::endl; } void connection_lost(const std::string& cause) override { std::cout << "connection lost: " << cause << std::endl; } }; int main() { mqtt::client client("tcp://127.0.0.1:1883", "async_sub"); user_callback cb; client.set_callback(&cb); mqtt::connect_options opts; opts.set_keep_alive_interval(30); opts.set_clean_session(false); // 持久会话,断线重连后不丢订阅 opts.set_automatic_reconnect(true); // 自动重连 client.connect(opts); client.subscribe("test/#", 1); // 主线程可以去做别的事,消息由回调处理 std::this_thread::sleep_for(std::chrono::seconds(60)); return 0; }这段代码里set_clean_session(false)的语义要理解对:broker 会为这个 clientId 保存订阅关系和离线消息。如果设成true,每次断线重连都要重新订阅,期间消息全丢。set_automatic_reconnect(true)是 paho.mqtt.cpp 在 VS2019 编译版里比较省心的特性,断线后库内部按退避策略重连,不用你写重连状态机。
5.2 用持久会话解决断线重连的消息补偿
真实项目里,设备端网络抖动是常态。用持久会话时,断线期间 broker 会按订阅时约定的 QoS 帮客户端缓存消息,重连后自动补发,这是 MQTT 协议自带的「后悔药」机制。但有几个限制:消息缓存只在 broker 内存里,broker 重启缓存就没了;QoS 0 的消息即使持久会话也不缓存;补发顺序不是严格全局有序的,和 broker 实现有关。
我一般会在业务侧做两层保险:订阅 QoS 设 1 或 2,同时本地维护一个消息序号,发现序号跳变就主动向 broker 拉一次全量同步。这样即使 broker 端丢了缓存,业务上也能发现对齐。用这份 VS2019 库时,持久会话的配置集中在connect_options上,和库的编译配置没有关系,完全看代码参数。
5.3 验证库与工程匹配的一站式自检清单
换机器、换工程、升级 VS 版本后,我每次都会强制走一遍这个验证流程:
- 先建一个空的控制台工程,不做任何业务代码,只包含
mqtt/client.h头文件,编译一次,确认头文件路径正确。 - 链接阶段加上 paho 的 lib,写一个不连 broker 的
mqtt::client client("tcp://127.0.0.1:1883", "test")构造,编译链接,确认符号解析干净。 - 起一个本地 broker(如 Mosquitto for Windows),跑第 2 章那段发布代码,确认
published OK打印出来。 - 用 MQTT X 或 mosquitto_sub 订阅
test/#,确认能收到hello from vs2019 paho。 - 核心验证一个点:把工程从 Release/x64 切到 Debug/Win32,重新编译跑一遍,确认四个组合都过。
这套流程走下来,库和工程的匹配问题基本能在五分钟内暴露完。从那以后我接手任何带预编译库的 VS 工程,都强制先跑一遍这五步,确认库包本身没问题再往业务代码里迁,省掉了很多「明明编译过了但一运行就崩」的排查时间,希望帮到你。
本文还有配套的精品资源,点击获取