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

0 阅读3分钟

一、 MQTT 简介

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

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

二、数据包格式

image.png

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

image.png

  • Flag: 标志位,PUBLISH报文标识

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

三、发布/订阅

image.png

  • 主题Topic:消息路由通道,类似于 URL 路径,使用/分层,订阅时可使用+或#通配符
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,添加监听配置
listener 1883 0.0.0.0
allow_anonymous true
  1. 打开Windows服务,重启服务
  2. 安装目录下,切到命令行,运行测试
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 发布者

#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 订阅者

#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;
}

五、资料参考