在Qt中实现mqtt客户端

文章目录

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为空)。


参考
【Windows下搭建MQTT服务器mosquitto】

相关推荐
4***571 小时前
PHP进阶-在Ubuntu上搭建LAMP环境教程
开发语言·ubuntu·php
ULTRA??1 小时前
C++拷贝构造函数的发生时机,深拷贝实现
开发语言·c++
zore_c1 小时前
【C语言】文件操作详解3(文件的随机读写和其他补充)
c语言·开发语言·数据结构·笔记·算法
CoderYanger1 小时前
动态规划算法-简单多状态dp问题:18.买卖股票的最佳时机Ⅳ
开发语言·算法·leetcode·动态规划·1024程序员节
Less is moree1 小时前
2.C语言文件操作(一):fgetc(),fgets(),fread的区别
c语言·开发语言·算法
CoderYanger1 小时前
动态规划算法-简单多状态dp问题:13.删除并获得点数
java·开发语言·数据结构·算法·leetcode·动态规划·1024程序员节
ohnoooo91 小时前
C++ STL库常用容器函数
java·开发语言
天骄t1 小时前
深入解析栈:数据结构与系统栈
java·开发语言·数据结构
源代码•宸1 小时前
GoLang并发示例代码1(关于逻辑处理器运行顺序)
开发语言·经验分享·后端·golang