物联网协议 MQTT 快速开发与实践

一、 MQTT 简介

MQTT (消息队列遥测传输,Message Queuing Telemetry Transport) 是OASIS和ISO制定的物联网消息协议,主要有3.1.1和5.0版规范,新项目普遍使用 MQTT 5.0。

  • MQTT5 特点
    • 基于TCP
    • 发布/订阅模式,核心是 MQTT 客户端 (Client) 和 MQTT代理(Broker)
    • 支持服务质量 (QoS) 等级、持久会话、会话管理、用户属性等

二、数据包格式

  • Packet type:15 种控制报文类型,可分为连接、发布、订阅三类
    • ACK:确认消息
    • REC、REL、COMP:保证交付,收到、释放、完成
  • Flag: 标志位,PUBLISH报文标识

    • Bit 3:DUP,是否重传报文。
    • Bit 2,1:QoS,服务质量等级。
    • Bit 0:Retain,是否保留消息。
  • Remaining Length:从第2个字节开始,可变头(Variable header) + 消息体(payload)的长度

三、发布/订阅

  • 主题Topic:消息路由通道,类似于 URL 路径,使用/分层,订阅时可使用+或#通配符
text 复制代码
sensor/1/temperature
sensor/2/temperature

sensor/+/temperature   //单层通配符
sensor/#               //多层通配符
  • QoS
    • QoS 0:消息最多传送一次。如果当前客户端不可用,它将丢失这条消息。
    • QoS 1:消息至少传送一次。
    • QoS 2:消息只传送一次。

四、开发实践

代码示例使用Mosquitto,当然Client也可以选Paho、Broker选EMQX、NanoMQ、HiveMQ等。

Broker (代理)

  1. 下载 mosquitto broker安装,安装时勾选"Service"组件
  2. 安装目录下打开mosquitto.conf,添加监听配置
text 复制代码
listener 1883 0.0.0.0
allow_anonymous true
  1. 打开Windows服务,重启服务
  2. 安装目录下,切到命令行,运行测试
bash 复制代码
mosquitto_sub -h localhost -t "test/topic" -v
# 运行下一句pub后收到: test/topic Hello MQTT

mosquitto_pub -h localhost -t "test/topic" -m "Hello MQTT3"

Client (发布者/订阅者)

  1. cmake编译mosquitto及所需依赖如cJson等,可选的如需要持久化添加SQLite依赖
  2. 创建C++项目,设置头文件包含目录和库目录,设置附加依赖项mosquitto.lib libcjson.lib...
  3. Client代码编译成功后,拷贝dll到exe目录,先运行订阅者,再运行发布者,否则消息后丢失

Publisher 发布者

cpp 复制代码
#include <iostream>
#include "mosquitto.h"

void on_connect(struct mosquitto* mosq, void* obj, int reason_code)
{
	printf("on_connect: %s\n", mosquitto_connack_string(reason_code));
	if (reason_code != 0)
		mosquitto_disconnect(mosq);
}

void on_publish(struct mosquitto* mosq, void* obj, int mid)
{
    printf("Message with mid %d has been published.\n", mid);
}

int main()
{
    mosquitto_lib_init();
    struct mosquitto* mosq = mosquitto_new(NULL, true, NULL);

    // 设置回调函数
    mosquitto_connect_callback_set(mosq, on_connect); // 收到CONNACK触发
    mosquitto_publish_callback_set(mosq, on_publish); // PUBLISH成功触发(QoS0) (QoS1:PUBACK , QoS2:PUBCOMP)

    mosquitto_connect(mosq, "127.0.0.1", 1883, 60);
    mosquitto_loop_start(mosq);

    //推送消息
    char payload[20] = {0};
    snprintf(payload, sizeof(payload), "temperature is %d", 26);
    mosquitto_publish(mosq, NULL, "example/temperature", (int)strlen(payload), payload, 2, false);

    getchar();

    mosquitto_lib_cleanup();

    return 0;
}

Subscriber 订阅者

cpp 复制代码
#include <iostream>
#include "mosquitto.h"

void on_connect(struct mosquitto* mosq, void* obj, int reason_code)
{
    printf("on_connect: %s\n", mosquitto_connack_string(reason_code));
    if (reason_code != 0)
        mosquitto_disconnect(mosq);

    //订阅,写在此处重连时能恢复
    mosquitto_subscribe(mosq, NULL, "example/temperature", 1);
}

// obj:mosquitto_new时的用户数据;mid:mosquitto_subscribe时的消息id;
// qos_count:Topic主题数量;granted_qos:每个订阅请求最终授予的 QoS
void on_subscribe(struct mosquitto* mosq, void* obj, int mid, int qos_count, const int* granted_qos)
{
    bool have_any_subscription = false;
    
    for (int i = 0; i < qos_count; i++) {
        printf("on_subscribe: %d:granted qos = %d\n", i, granted_qos[i]);
        if (granted_qos[i] <= 2) {
			have_any_subscription = true;//qos 0,1,2表示订阅成功,128表示订阅失败
        }
    }
    if (have_any_subscription == false) {
        fprintf(stderr, "Error: All subscriptions rejected.\n");
        mosquitto_disconnect(mosq);
    }
}

void on_message(struct mosquitto* mosq, void* obj, const struct mosquitto_message* msg)
{
    printf("%s %d %s\n", msg->topic, msg->qos, (char*)msg->payload);
}

int main()
{
    mosquitto_lib_init();

    struct mosquitto* mosq = mosquitto_new(NULL, true, NULL);

    // 设置回调函数
    mosquitto_connect_callback_set(mosq, on_connect);     // 收到CONNACK触发
    mosquitto_subscribe_callback_set(mosq, on_subscribe); // 收到SUBACK触发,即订阅成功
    mosquitto_message_callback_set(mosq, on_message);     // 收到消息触发

    mosquitto_connect(mosq, "127.0.0.1", 1883, 60);

    mosquitto_loop_start(mosq);

    getchar();

    mosquitto_lib_cleanup();

    return 0;
}

五、资料参考

相关推荐
念念清晰2 分钟前
000-OpenJDK17 源码构建:MacOS环境(M芯片)
java·后端
来福猿31 分钟前
SpringCloud Config 源码分析:配置拉取、刷新与多环境管理
后端·spring·spring cloud
IT_陈寒2 小时前
React组件莫名重渲染?你可能忘了这个小细节
前端·人工智能·后端
钱工的门窗技术笔记2 小时前
智能门窗的边界与前提:为什么“智能”不能替代本体性能
网络·物联网·智能家居
用户9888321871743 小时前
给 .NET 网络调用设定“预算”:用 CancellationTokenSource.CancelAfter 处理超时取消
后端
清风儿3293 小时前
一块不会说话的电表,把我们折腾了半个月
后端
预知同行3 小时前
深入解析 MoE 混合专家模型:从 Router 到 All-to-All 的架构全景与生产实践
后端·架构
upala3 小时前
【仓颉语言入门 · 第二篇 第 8 课】
后端
weiwin1233 小时前
MAF 入门(8):多模态实战——让 Agent 看懂图片、处理混合内容
人工智能·后端
弈栈录4 小时前
流式对话接口设计:SSE、WebSocket 与断线重连
后端