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级别),可以根据需要选择消息的可靠性。
- 最多一次,这一级别会发生消息丢失或重复,消息发布依赖于底层TCP/IP网络。即:<=1
- 至少一次,这一级别会确保消息到达,但消息可能会重复。即:>=1
- 只有一次,确保消息只有一次到达。即:=1。在一些要求比较严格的计费系统中,可以使用此级别
- 灵活性:支持动态创建和销毁主题,客户端可以根据需求订阅感兴趣的主题。
- 异步性:消息的发布和订阅是异步的,客户端无需等待确认。
2. MQTT协议原理
2.1 MQTT协议实现方式
- 实现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协议简介及协议原理