物联网协议 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;
}

五、资料参考

相关推荐
wo_jack1 小时前
C++ 入门笔记(十六):第16课 vector 模拟实现十问十答 —— 从三指针到迭代器失效
c++·后端·面试·stl·vector
量化分析码农2 小时前
【Python量化波动率实战 02】波动率会传染:EWMA 指数加权给近期行情更大话语权
后端
GISer_Jing3 小时前
如何学习Agent
后端·langchain
JWASX4 小时前
【Go】map 源码解析 - group
后端·golang·map·源码分析
量化分析码农5 小时前
【Python量化波动率实战 01】波动率是怎么算的:年化、滚动与区间估计一次讲清
后端
特立独行的猫A5 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python
金銀銅鐵5 小时前
从一个类名到一张类图:用反射自动生成 PlantUML
java·后端·uml
追逐时光者6 小时前
一款基于 .NET 开源、免费的数据库自治诊断平台
后端·.net
追逐时光者6 小时前
让 GitHub Copilot 更懂你的 .NET 代码风格:一份精简实用的 Copilot Instructions
后端·.net
Eyfcom7 小时前
〖物联网终端实战 02〗RFID 批量群读防碰撞算法:整托盘入库如何做到“秒级”识别
物联网·物流系统·快递系统·跨境物流快递系统·物联网终端实战