Ubuntu 基于C++实现MQTT通信

MQTT简介

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

1. MQTT协议概述

MQTT协议是IBM开发的一种发布/订阅消息传递协议。它采用发布-订阅模式,支持多个客户端同时连接到MQTT服务器(也称为MQTT代理或MQTT broker),并通过主题(Topic)进行消息的发布和订阅。

MQTT协议的特点:

  • 轻量级:MQTT协议设计简单,协议头部较小(只有2字节),适合在带宽有限的环境下使用。
  • 可靠性:支持三种消息传递质量等级(QoS级别),可以根据需要选择消息的可靠性。
    1. 最多一次,这一级别会发生消息丢失或重复,消息发布依赖于底层TCP/IP网络。即:<=1
    2. 至少一次,这一级别会确保消息到达,但消息可能会重复。即:>=1
    3. 只有一次,确保消息只有一次到达。即:=1。在一些要求比较严格的计费系统中,可以使用此级别
  • 灵活性:支持动态创建和销毁主题,客户端可以根据需求订阅感兴趣的主题。
  • 异步性:消息的发布和订阅是异步的,客户端无需等待确认。

2. MQTT协议原理

2.1 MQTT协议实现方式
11

  • 实现MQTT协议需要:客户端和服务器端
  • MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
  • MQTT传输的消息分为:主题(Topic)和负载(payload)两部分
    • Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)
    • payload,可以理解为消息的内容,是指订阅者具体要使用的内容

2.2 网络传输与应用消息

MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。

当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关连。

2.3 MQTT通信流程如下:

  • 客户端连接到MQTT服务器,并建立TCP连接。
  • 客户端向服务器发送连接请求,并提供客户端ID等信息。
  • 服务器接受连接请求,并回复确认连接。
  • 客户端可以发布消息到指定的主题,也可以订阅感兴趣的主题。
  • 服务器接收客户端发布的消息,并将消息转发给订阅该主题的客户端。

3. MQTT库

为了在Linux环境下实现MQTT通信,我们可以使用现有的MQTT库来简化开发过程。常用的MQTT库包括:

  • Eclipse Paho:Eclipse Paho是一个开源的MQTT实现,支持多种编程语言,包括C、C++、Java、Python等。
  • Mosquitto:Mosquitto是一个轻量级的MQTT代理,同时也提供了C库用于实现MQTT客户端。

4. MQTT客户端与服务器端实现

MQTT客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h> // For sleep()
#include <MQTTClient.h>

#define ADDRESS "tcp://localhost:1883"
#define CLIENTID "Client"
#define TOPIC "test"
#define QOS 1
#define TIMEOUT 10000L

// 发出退出信号的全局变量
volatile int keepRunning = 1;

// Signal handler to set the exit flag
void handle_signal(int signal) {
    keepRunning = 0;
}

int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) {
    if (message == NULL) {
        printf("Received a NULL message.\n");
        return 0;
    }
    printf("Message arrived callback triggered.\n");

    // 确保缓冲区不溢出
    char payload[1024];
    size_t payload_len = message->payloadlen < sizeof(payload) - 1 ? message->payloadlen : sizeof(payload) - 1;
    strncpy(payload, (char *)message->payload, payload_len);
    payload[payload_len] = '\0';
    printf("Message arrived on topic %s: %s\n", topicName, payload);

    // Set exit flag if message is "Q" or "q"
    if (strcmp(payload, "Q") == 0 || strcmp(payload, "q") == 0) {
        printf("Exit command received. Exiting...\n");
        keepRunning = 0;
    }

    MQTTClient_freeMessage(&message);
    MQTTClient_free(topicName);
    return 1; // 确保返回值
}

int main() {
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    MQTTClient_message *message = NULL;
    MQTTClient_deliveryToken token;
    int rc;

    // Register signal handler
    signal(SIGINT, handle_signal);

    // 创建客户端
    rc = MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to create client, return code %d\n", rc);
        return EXIT_FAILURE;
    }
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;

    // 设置回调函数
    rc = MQTTClient_setCallbacks(client, NULL, NULL, messageArrived, NULL);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to set callbacks, return code %d\n", rc);
        MQTTClient_disconnect(client, 10000);
        MQTTClient_destroy(&client);
        return EXIT_FAILURE;
    }  else  {
        printf("success to set callbacks, return code %d\n", rc);
    }

    // 连接到 MQTT 服务器
    rc = MQTTClient_connect(client, &conn_opts);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to connect, return code %d\n", rc);
        MQTTClient_destroy(&client);
        return EXIT_FAILURE;
    }

    // 订阅主题
    rc = MQTTClient_subscribe(client, TOPIC, QOS);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to subscribe to topic %s, return code %d\n", TOPIC, rc);
        MQTTClient_disconnect(client, 10000);
        MQTTClient_destroy(&client);
        return EXIT_FAILURE;
    }  else {
        printf("Subscribed to topic: %s with QoS: %d\n", TOPIC, QOS);
    }

    // 等待消息到来
    // 方法一:信号量
    while (keepRunning) {
        sleep(1); // Sleep for a while to reduce CPU usage
                  // Wait for messages to arrive
    }

    // 方法二:
    // printf("Press Q<Enter> to quit.\n");
    // char c;
    // do {
    //     c = getchar();
    // } while (c != 'Q' && c != 'q');

    // 断开连接并销毁客户端
    MQTTClient_unsubscribe(client, TOPIC);
    MQTTClient_disconnect(client, 10000);
    MQTTClient_destroy(&client);
    return EXIT_SUCCESS;
}

MQTT服务端:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <MQTTClient.h>

#define ADDRESS "tcp://localhost:1883"
#define CLIENTID "Server"
#define TOPIC "test"
#define QOS 1
#define TIMEOUT 10000L

// 添加一个回调函数来处理发布完成后的操作
void messageDelivered(void *context, MQTTClient_deliveryToken dt) {
    printf("MessageDelivered with delivery token %d delivered.\n", dt);
}

// 空的消息到达回调函数
int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) {
    // 如果不需要处理消息,可以返回 1,表示消息已经被处理
    return 1;
}

// 空的连接丢失回调函数
void connectionLost(void *context, char *cause) {
    printf("Connection lost, cause: %s\n", cause);
}

int main() {
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    int rc;
    
    rc = MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
    if (rc != MQTTCLIENT_SUCCESS){
        printf("Failed to create client, return code %d\n", rc);
        return EXIT_FAILURE;
    }

    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;

    // 连接 MQTT 代理
    if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)  {
        printf("Failed to connect, return code %d\n", rc);
        return EXIT_FAILURE;
    }
    
    // 设置回调函数,提供完整的回调函数集
    rc = MQTTClient_setCallbacks(client, NULL, connectionLost, messageArrived, messageDelivered);
    if (rc != MQTTCLIENT_SUCCESS) {
        printf("Failed to set callbacks, return code %d\n", rc);
        MQTTClient_disconnect(client, 10000);
        MQTTClient_destroy(&client);
        return EXIT_FAILURE;
    }  else  {
        printf("success to set callbacks, return code %d\n", rc);
    }

    while (1) {
        char message[1024];
        printf("Enter message (type 'exit' to quit): ");
        fgets(message, sizeof(message), stdin);
        message[strcspn(message, "\n")] = '\0';

        if (strcmp(message, "exit") == 0) {
            break;
        }

        //  初始化一个 MQTT 消息结构体,配置消息内容(payload)、长度、QoS 等参数
        MQTTClient_message pubmsg = MQTTClient_message_initializer;
        pubmsg.payload = message;
        pubmsg.payloadlen = strlen(message);
        pubmsg.qos = QOS;
        pubmsg.retained = 0; // 设置为 0,表示不保留该消息

        MQTTClient_deliveryToken token;  
        int rc = MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token); //阻塞程序,直到消息传递完成或达到超时
        if (rc != MQTTCLIENT_SUCCESS) {
            printf("Failed to publish message, return code %d\n", rc);
            // 在这里可以执行其他错误处理逻辑,比如重试发布或者退出程序
        }

        printf("Waiting for up to %d seconds for publication of %s\n"
               "on topic %s for client with ClientID: %s\n",
               (int)(TIMEOUT / 1000), message, TOPIC, CLIENTID);

        rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
        if (rc != MQTTCLIENT_SUCCESS) {
            printf("Failed to wait for message delivery, return code %d\n", rc);
        }  else {
            printf("Message with delivery token %d delivered\n", token);
        }
    }

    MQTTClient_disconnect(client, 10000); // 断开与代理的连接
    MQTTClient_destroy(&client);          // 销毁 MQTT 客户端实例,释放资源
    return rc;
}

4.1 编译和运行
将以上代码分别保存为mqtt_server_test.cpp和mqtt_client_test.cpp,并使用以下命令编译:

gcc -o mqtt_server mqtt_server_test.cpp -lpaho-mqtt3c
gcc -o mqtt_client mqtt_client_test.cpp -lpaho-mqtt3c

然后运行服务器端和客户端:

./mqtt_server
./mqtt_client

客户端会向服务器端发送一条消息,并通过回调函数接收服务器端发送的消息。

参考:
mqtt实现:https://github.com/STRIVING-NICE/mqtt_protocol.git
Linux MQTT通信:实现轻量级物联网传输协议
MQTT协议-MQTT协议简介及协议原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值