文章目录
1.前言
目前我使用的Qt版本为Qt5.15.2,虽然有一个和qt结合得很好的mqtt库:qmqtt
但是由于其不再维护,可能有一些新标准啥的没及时跟上,后续可能会逐渐落伍甚至无法和市场主流的服务器通讯,这里还是选了一个还在继续维护的开源项目:https://github.com/eclipse-paho/paho.mqtt.c
2.mqtt服务端
在正式在qt中创建mqtt客户端之前,我们先搞定mqtt服务器,有这个服务器我们才好调试。
经过一番搜索后,发现现在主流的一般是用这两个服务程序:mosquitto、emqx。mosquitto好多都需要用命令行来操作,比较麻烦;emqx有个网页界面,操作起来方便一点,那就选emqx了。
2.1.下载软件
到其官网下载:
https://www.emqx.com/zh/try?tab=self-managed
下载个edge就够了。免费版最多只能建立10个连接,但是也够用了。


这个软件是绿色版,免安装的,解压就可以用。
下载后,解压到某个文件夹下:

2.2.启动软件
在启动之前,先修改一下nanomq.conf这个文件的内容,否则其ssl加密的服务(也就是wss、mqtts)不会起来。
把listeners.ssl下的enable值从false改为true

然后再打开cmd,cd到你把emqx-edge解压到的文件夹,然后执行
bash
nanomq.exe start
正常的话,你应该可以在命令行中看到五个服务被启动了:

| 服务 | 端口 | 简介 |
|---|---|---|
| mqtt | 1883 | MQTT 代理(Broker) |
| ws | 8083 | MQTT 代理(Broker) |
| mqtts | 8883 | MQTT 代理(Broker) |
| wss | 8086 | MQTT 代理(Broker) |
| http | 8081 | 提供了网页功能,用户通过访问此链接,就可以对服务器进行监控 |
2.3.监控
然后我们可以直接在网页浏览器中输入:http://127.0.0.1:8081,访问服务器配置页面

用户名、密码在其配置页面中,

登录完成:

这个页面的功能可以自行摸索或者阅读官方文档,这里不再赘述。
至此,服务器我们已经搭建起来了。
3.mqtt客户端
目前我们是在windows下使用,可以直接使用官方为我们编译好的库。假如是想集成到嵌入式系统内,需要自行拿源码进行编译。

然后修改一下我们工程的pro文件:
头文件和库文件路径根据你的实际路径来写
bash
# 在Windows下要添加这边,不然会与paho.mqtt.c有冲突,无法顺利编译
LIBS += -lws2_32 -liphlpapi
DEFINES += WIN32_LEAN_AND_MEAN
# 服务器的创建可以使用emqx edge https://www.emqx.com/zh/downloads-and-install/emqx-edge?os=Windows
# 使用的库为 https://github.com/eclipse-paho/paho.mqtt.c
INCLUDEPATH += $$PWD/../eclipse-paho-mqtt-c/include
LIBS += -L$$PWD/../eclipse-paho-mqtt-c/lib \
-lpaho-mqtt3cs -lpaho-mqtt3a -lpaho-mqtt3as -lpaho-mqtt3c
然后就可以写代码测试一下了
主要假如使用ssl方式时,需要指定证书,证书就用服务器提供的证书etc/certs/cacert.pem即可

cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
// #define ADDRESS "ws://127.0.0.1:8083"
#define ADDRESS "wss://127.0.0.1:8086"
// #define ADDRESS "mqtt://127.0.0.1:1883"
// #define ADDRESS "mqtts://127.0.0.1:8883"
#define CLIENTID "NanoMQ_MjI3Nz"
#define TOPIC "MY_TOPIC"
#define PAYLOAD "Hello World!"
#define QOS 1
#define TIMEOUT 10000L
volatile MQTTClient_deliveryToken deliveredtoken;
void delivered(void *context, MQTTClient_deliveryToken dt)
{
qDebug("Message with token value %d delivery confirmed\n", dt);
deliveredtoken = dt;
}
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
qDebug("Message arrived\n");
qDebug(" topic: %s\n", topicName);
qDebug(" message: %.*s\n", message->payloadlen, (char*)message->payload);
MQTTClient_freeMessage(&message);
MQTTClient_free(topicName);
return 1;
}
void connlost(void *context, char *cause)
{
qDebug("\nConnection lost\n");
if (cause)
qDebug(" cause: %s\n", cause);
}
int mqttClientTest()
{
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
int rc;
const char* uri = ADDRESS;
qDebug("Using server at %s\n", uri);
if ((rc = MQTTClient_create(&client, uri, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
{
qDebug("Failed to create client, return code %d\n", rc);
rc = EXIT_FAILURE;
goto exit;
}
if ((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS)
{
qDebug("Failed to set callbacks, return code %d\n", rc);
rc = EXIT_FAILURE;
goto destroy_exit;
}
// 1. 定义SSL选项
MQTTClient_SSLOptions sslOpt = MQTTClient_SSLOptions_initializer;
// 2. 根据你的证书来源设置路径
// 场景一:使用从服务商下载或自己导出的CA证书(单向认证)
sslOpt.trustStore = "C:/Users/Administrator/Desktop/jsEngine/emqx-edge-1.2.0-windows-x86_64/etc/certs/cacert.pem"; // 注意Windows路径使用双反斜杠
sslOpt.enableServerCertAuth = 1;
// 场景二:服务器要求双向认证(还需要客户端证书和私钥)
// sslOpt.trustStore = "C:\\your_project\\certs\\ca.pem";
// sslOpt.keyStore = "C:\\your_project\\certs\\client.pem"; // 你的客户端证书
// sslOpt.privateKey = "C:\\your_project\\certs\\client.key"; // 你的客户端私钥
// 如果私钥有密码,还需要设置:
// sslOpt.privateKeyPassword = "your_key_password";
// 3. 配置结构体版本和ID(必须)
sslOpt.struct_version = 4; // 使用支持ALPN的版本,对WSS连接很重要
strncpy(sslOpt.struct_id, "MQTS", 4);
conn_opts.ssl = &sslOpt;
// conn_opts.username = "123";
// conn_opts.password = "123";
conn_opts.keepAliveInterval = 60;
conn_opts.cleansession = 1;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
qDebug("Failed to connect, return code %d\n", rc);
rc = EXIT_FAILURE;
goto destroy_exit;
}
qDebug("Subscribing to topic %s\nfor client %s using QoS%d\n\n"
"Press Q<Enter> to quit\n\n", TOPIC, CLIENTID, QOS);
if ((rc = MQTTClient_subscribe(client, TOPIC, QOS)) != MQTTCLIENT_SUCCESS)
{
qDebug("Failed to subscribe, return code %d\n", rc);
rc = EXIT_FAILURE;
}
else
{
int ch;
do
{
ch = getchar();
} while (ch!='Q' && ch != 'q');
if ((rc = MQTTClient_unsubscribe(client, TOPIC)) != MQTTCLIENT_SUCCESS)
{
qDebug("Failed to unsubscribe, return code %d\n", rc);
rc = EXIT_FAILURE;
}
}
if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)
{
qDebug("Failed to disconnect, return code %d\n", rc);
rc = EXIT_FAILURE;
}
destroy_exit:
MQTTClient_destroy(&client);
exit:
return rc;
}
运行后,发布一个信息,qt这边可以顺利收到。完美。


4.mqtt的一些知识点
4.1.ClientID
有时我们发现在A软件上对mqtt服务器建立连接后,再在另外一个软件上对服务器建立同样的连接,先前的连接会被挤掉。这时候看看是不是使用了同样的ClientID。只要使用不同的ClientID,就不会被挤掉。
4.2.Retain
假如我们发布了一个Retain信息,那么客户端在连接上之后,服务器会自动发一次最新发布的信息给客户端;假如这时想取消,只需再发一次Recain空信息即可(payload为空)。