MQTT设计与实现

目录

前言

一、MQtt简介

二、MQTT代理

1.Mqtt代理(Broker)简介

2.开源MQTT代理对比

三、搭建开源Mqtt Broker

01.开源MQTT代理1:Mosquitto

1.安装Mosquitto Broker:

02.开源MQTT代理2:EMQX Broker

四、MQTT客户端设计

1、功能接口分析

2、在Qt中MQTT客户端(C++)的设计实现

2.1、QT 部署官方MQTT模块

2.2、具体实现Demo

待更新:

在Linux中MQTT客户端(C)的设计与实现

在STM32中MQTT客户端(C)的设计与实现



前言

MQTT协议是应用广泛的物联网通讯协议,在物联网应用中非常重要。此文是本人从零开始,经过查阅学习相关资料的总结。本文主要包含四个部分: 一是Mqtt协议的简介,此部分介绍了mqtt模型等;二是Mqtt Broker的介绍;三是Mqtt 客户端的设计与实现,此部分包含C++(在Qt中,利用Qt库实现);C(在linux环境下,利用paho接口实现) ; 四是关于MQTT的一些个人总结。


一、MQtt简介

        MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是IBM开发的一个物联网通讯协议,OASIS(结构化信息标准促进组织)已宣布MQTT协议作为其新兴的物联网消息传递协议的首选。在MQTT的官方网站上,定义MQTT是一种machine-to-machine (M2M)设备之间通信的物联网互联协议,是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

MQTT设计与实现_第1张图片

        MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是MQTT服务器,消息发布者可以同时是订阅者。

        在项目实际应用中,包含以下三个角色参与通信:

(1)MQTT代理服务程序,负责接收、存储、转发MQTT消息;

(2)MQTT客户端,一般情况下指的是手机APP程序,为用户提供一个交互界面,负责发布控制命令或查询设备状态信息等;

(3)后台服务端,指的是后台管理系统,负责与数据库交互,为网关和移动终端提供数据服务;实际上后台服务端在整个系统中也是一个MQTT客户端

MQTT传输的消息分为主题(Topic)和负载(payload)两部分:

Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)。

payload,可以理解为消息的内容,是指订阅者具体要使用的内容,对应我们的通信协议的消息包。

二、MQTT代理


1.Mqtt代理(Broker)简介

MQTT服务器以称为“消息代理”(Broker),可以是一个应用程序或一台设备。它是位于消息发布者和订阅者之间,它可以:

(1)接受来自客户的网络连接;

(2)接受客户发布的应用信息;

(3)处理来自客户端的订阅和退订请求;

(4)向订阅的客户转发应用程序消息。

2.开源MQTT代理对比

物联网行业里可选的MQTT Broker有很多,除了经典的Mosquitto和AWS、Azure,百度云、阿里云、IBM等几个提供物联网MQTT接入服务的产品外,可用于商业生产的MQTT Broker还有多款。

本文选取了几个热门开源的 MQTT Broker,其中部分项目提供商业支持,做简单选型对比。

对比项目3. EMQ HiveMQ VerneMQ ActiveMQ Mosquitto
License 开源+商业版 开源+商业版 开源+商业版 开源 开源
公司/社区 EMQ HiveMQ VerenMQ Apache 基金会 Eclipse 基金会
开源协议 Apache License 2.0 Apache License 2.0 Apache License 2.0 Apache License 2.0 EPL/EDL licensed
开发团队 杭州映云科技有限公司 dc-square 股份有限公司,德国 Octavo Labs AG,瑞士 Apache 项目维护者 Eclipse 开源社区
开发语言 Erlang Java Erlang Java C
项目历史 2012年开始开源,2016年开始商业化 2013 年成立,一直以闭源方式向客户提供软件,2019 年开源 提供基于开源的商业化定制服务 2004 由 LogicBlaze 创建;原本规划的 ActiveMQ 的下一代开源项目 Apollo 已经不活动(4年没有代码更新)
集群架构 支持 仅企业版 支持 支持 不支持(有伪集群实现)
系统部署 物理机、虚拟机、K8S 物理机、虚拟机、K8S 物理机、虚拟机、K8S 物理机、虚拟机、容器 物理机、虚拟机、容器
支持协议 MQTT、CoAP、MQTT-SN、WebSocket、TCP、UDP、LwM2M MQTT MQTT JMS、Openwire、Stomp、AMQP、MQTT、WebSocket XMPP MQTT、WebSocket
系统性能 单机性能较高,单机支持百万级并发,集群支持千万级并发 集群支持千万级并发 集群支持百万级并发 支持集群 单机10W
MQTT v3.1,v3.1.1,v5.0 v3.1,v3.1.1,v5.0 v3.1,v3.1.1,v5.0 v3.1 v3.1,v3.1.1,v5.0
边缘计算 EMQ X Edge 支持树莓派,ARM 等架构,支持数据同步到云服务 Azure IoT Hub AWS 不支持 不支持 不支持 支持(自身比较轻量)
安全与认证 TLS/DTLS、X.509证书、JWT、OAuth2.0、应用协议(ID/用户名/密码)、数据库与接口形式的认证与 ACL 功能(LDAP、DB、HTTP) TLS/DTLS、X.509证书、JWT、OAuth2.0、应用协议(ID/用户名/密码)、配置文件形式的认证与 ACL 功能 TLS/DTLS、X.509证书、配置文件形式的认证与 ACL 功能、数据库形式的认证与 ACL 功能,但支持数据库较少 LDAP (JAAS)、Apache Shiro 等待
运行持久化 支持将消息数据持久化至外部数据库如 Redis、MySQL、PostgreSQL、MongoDB、Cassa、Dynamo 等,需企业版,开源版宕机则丢失 开源企业均支持本地持久化,采用磁盘系统,支持备份,导出备份 支持持久化至 Google LevelDB AMQ、KahaDB、JDBC、LevelDB 等待
扩展方式 Webhook、Trigger、Plugin 等,支持 Erlang 与 Lua、Java、Python 扩展开发,支持 Webhook 开发,侵入性不强 Trigger、Plugin 等,使用 Java 技术栈开发,提供方便开发的 SDK Trigger、Plugin 等,支持 Erlang 与 Lua 扩展开发 Java 扩展 等待
数据存储 企业版适配数据库:Redis、Mysql、PostgreSQL、MongoDB、Cassandra、OpenTSDB、TimescaleDB、InfluxDB 适配消息队列:Kakfa、RabbitMQ、Pulsar 桥接模式:支持桥接至标准 MQTT 协议消息服务
开源版支持 HTTP 将数据同步、存储
适配数据库:无,提供 Java SDK 开发进行适配
消息队列:Kafka 桥接模式:支持桥接至标准 MQTT 协议消息服务
适配数据库:无,提供 Erlang 和 Lua 扩展开发 适配消息队列:无 桥接模式:支持桥接至标准 MQTT 协议消息服务 适配数据库:JDBC、KahaDB、LevelDB 适配消息队列:无 桥接模式:支持通过 JMS 桥接 等待
管理监控 支持可视化的 Dashboard,实现集群与节点的统一集中管理 支持第三方监控工具 Prometheus ,提供可视化 Grafana 界面模板 支持可视化的 HiveMQ Control Center,实现集群与节点统一管理 支持第三方监控工具 Prometheus ,可提供可视化 Grafana 界面 支持 InfluxDB 监控 内置简单状态管理可视化界面 支持第三方监控工具 Prometheus ,可提供可视化 Grafana 界面 支持可视化的监控界面 支持第三方监控工具 Prometheus ,可提供可视化 Grafana 界面 通过 MQTT 订阅系统主题
规则引擎 支持规则引擎,基于 SQL 的规则引擎给予 Broker 超越一般消息中间件的能力。除了在接受转发消息之外,规则引擎还可以解析消息的格式(企业版)。
规则引擎由消息的订阅,发布,确认的事件触发,根据消息的负载来执行相应的动作,降低应用开发的复杂度。
不支持 不支持 不支持 不支持
开发集成 支持通过 REST API 进行常用的业务管理操作如: 调整设置、获取 Broker 状态信息、进行消息发布、代理订阅与取消订阅、断开指定客户端、查看客户端列表、规则引擎管理、插件管理,提供 Java SDK、Python SDK 直接编码处理业务逻辑 无,提供 Java SDK 在应用系统在编码的层面操作进程,非常灵活但耦合性高 提供少量 REST API,用于监控与状态管理、客户端管理等。 缺乏代理订阅、业务管理等功能和 API 提供少量队列管理 REST API 等待
适用场景 优势在于高并发连接与高吞吐消息的服务能力,以及物联网协议栈支持的完整性;扩展能力较强,无需过多开发 有一定高并发连接与高吞吐消息的服务能力,物联网协议栈的完整性较弱仅支持 MQTT 协议;缺乏开箱即用的功能插件,功能必须编码使用 基础的并发连接与高吞吐消息的服务能力,物联网协议栈的完整性较弱仅支持 MQTT 协议;扩展能力较差,基础的业务组件支持度不够,商业成熟度不足客户量较少,缺乏开箱即用的功能插件   核心是消息队列系统,主要用于支持异构应用之间的消息通信,比如用于企业消息总线等;后面支持了部分物联网协议。ActiveMQ 比较适合系统既要支持传统的异构应用之间需要通信,也需要支持小型物联网接入支持的用户。 轻量简便的 MQTT Broker,工控、网关或小规模接入项目

三、搭建开源Mqtt Broker

01.开源MQTT代理1:Mosquitto

MQTT Broker 即MQTT 消息服务器,实际上是一个TCP服务端。在这里我们选择搭建轻量的mosquitto服务器作为演示,快速搭建一个MQTT Broker,使初步接触的同学们有一个快速的了解。我使用的是腾讯云的轻量型云服务器,系统版本为:Ubuntu 20.04

参考文章:https://blog.csdn.net/qq_28632173/article/details/84933527

1.安装Mosquitto Broker:

1.安装mosquitto
apt-get install mosquitto

2.查看状态
service mosquitto status

3.配置文件详解(mosquitto安装后,其配置文件在 /etc/mosquitte/conf.d目录下
# =================================================================
# General configuration
# =================================================================

# 客户端心跳的间隔时间
#retry_interval 20

# 系统状态的刷新时间
#sys_interval 10

# 系统资源的回收时间,0表示尽快处理
#store_clean_interval 10

# 服务进程的PID
#pid_file /var/run/mosquitto.pid

# 服务进程的系统用户
#user mosquitto

# 客户端心跳消息的最大并发数
#max_inflight_messages 10

# 客户端心跳消息缓存队列
#max_queued_messages 100

# 用于设置客户端长连接的过期时间,默认永不过期
#persistent_client_expiration

# =================================================================
# Default listener
# =================================================================

# 服务绑定的IP地址
#bind_address

# 服务绑定的端口号
#port 1883

# 允许的最大连接数,-1表示没有限制
#max_connections -1

# cafile:CA证书文件
# capath:CA证书目录
# certfile:PEM证书文件
# keyfile:PEM密钥文件
#cafile
#capath
#certfile
#keyfile

# 必须提供证书以保证数据安全性
#require_certificate false

# 若require_certificate值为true,use_identity_as_username也必须为true
#use_identity_as_username false

# 启用PSK(Pre-shared-key)支持
#psk_hint

# SSL/TSL加密算法,可以使用“openssl ciphers”命令获取
# as the output of that command.
#ciphers

# =================================================================
# Persistence
# =================================================================

# 消息自动保存的间隔时间
#autosave_interval 1800

# 消息自动保存功能的开关
#autosave_on_changes false

# 持久化功能的开关
persistence true

# 持久化DB文件
#persistence_file mosquitto.db

# 持久化DB文件目录
#persistence_location /var/lib/mosquitto/

# =================================================================
# Logging
# =================================================================

# 4种日志模式:stdout、stderr、syslog、topic
# none 则表示不记日志,此配置可以提升些许性能
log_dest none

# 选择日志的级别(可设置多项)
#log_type error
#log_type warning
#log_type notice
#log_type information

# 是否记录客户端连接信息
#connection_messages true

# 是否记录日志时间
#log_timestamp true

# =================================================================
# Security
# =================================================================

# 客户端ID的前缀限制,可用于保证安全性
#clientid_prefixes

# 允许匿名用户
#allow_anonymous true

# 用户/密码文件,默认格式:username:password
#password_file

# PSK格式密码文件,默认格式:identity:key
#psk_file

# pattern write sensor/%u/data
# ACL权限配置,常用语法如下:
# 用户限制:user 
# 话题限制:topic [read|write] 
# 正则限制:pattern write sensor/%u/data
#acl_file

# =================================================================
# Bridges
# =================================================================

# 允许服务之间使用“桥接”模式(可用于分布式部署)
#connection 
#address [:]
#topic  [[[out | in | both] qos-level] local-prefix remote-prefix]

# 设置桥接的客户端ID
#clientid

# 桥接断开时,是否清除远程服务器中的消息
#cleansession false

# 是否发布桥接的状态信息
#notifications true

# 设置桥接模式下,消息将会发布到的话题地址
# $SYS/broker/connection//state
#notification_topic

# 设置桥接的keepalive数值
#keepalive_interval 60

# 桥接模式,目前有三种:automatic、lazy、once
#start_type automatic

# 桥接模式automatic的超时时间
#restart_timeout 30

# 桥接模式lazy的超时时间
#idle_timeout 60

# 桥接客户端的用户名
#username

# 桥接客户端的密码
#password

# bridge_cafile:桥接客户端的CA证书文件
# bridge_capath:桥接客户端的CA证书目录
# bridge_certfile:桥接客户端的PEM证书文件
# bridge_keyfile:桥接客户端的PEM密钥文件
#bridge_cafile
#bridge_capath
#bridge_certfile
#bridge_keyfile

# 自己的配置可以放到以下目录中
include_dir /etc/mosquitto/conf.d

02.开源MQTT代理2:EMQX Broker

        Emqx broker是本次主要介绍的一款国产MQTT代理。按照网上所说,性能上EMQX单机版支持百万并发,集群支持千万并发,性能上完胜其他mqtt服务器。抛开性能不说,由于EMQX国产,支持国产,相对其他代理来说,EMQX具有丰富的中文文档资料,查阅方便,EMQX官网也有简洁的MQTT相关知识的介绍,对于初学者绝对是一个很好的选择。

        这里先给出EMQX官方文档地址:产品概览 | EMQX 文档

        安装部署的说明直接根据官方的文档引导进行即可。

四、MQTT客户端设计

1、功能接口分析

连接、发布、订阅、服务质量、ping(后续会更新,其实也是一些知识点的搬运)

2、在Qt中MQTT客户端(C++)的设计实现

2.1、QT 部署官方MQTT模块

在Qt Creator中,默认是没有部署Mqtt模块的,在使用QT编程之前,先把mqtt 模块部署到开发环境当中。参考博文:Qt开发技术:mqtt介绍、QtMqtt编译和开发环境搭建_长沙红胖子-CSDN博客

  • 下载

Qt官方在github上提供了源代码,地址:https://github.com/qt/qtmqtt.选择对应的版本下载

MQTT设计与实现_第2张图片

  • 编译源码

打开源码文件,双击.pro打开工程文件,选择release编译。

MQTT设计与实现_第3张图片

MQTT设计与实现_第4张图片

编译这个源码需要安装perl,否则会报错:perl 不是内部或外部命令,也不是可运行的程序。

perl下载地址:https://www.perl.org/get.html

安装完Perl后会自动写入环境变量, 这时候再次编译(Release模式)QtMqtt源码,编译完成后得到以下文件:

MQTT设计与实现_第5张图片

  • 将Mqtt模块布署到QT环境中

2.2、具体实现Demo

  • MqttDemo.pro

QT       += core gui mqtt

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
  • mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 
#include 
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void updateLogStateChange();

    void brokerDisconnected();

    void message_rcv(const QByteArray &message, const QMqttTopicName &topic);

    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::MainWindow *ui;
    QMqttClient *mqtt_client;


};
#endif // MAINWINDOW_H
  • mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    mqtt_client = new QMqttClient(this);

    connect(mqtt_client, &QMqttClient::stateChanged, this, &MainWindow::updateLogStateChange);    //状态已更改
    connect(mqtt_client, &QMqttClient::disconnected, this, &MainWindow::brokerDisconnected);      //与代理断开
    connect(mqtt_client, &QMqttClient::messageReceived, this, &MainWindow::message_rcv);          //接收消息
}

MainWindow::~MainWindow()
{
    delete ui;
    delete mqtt_client;
}


void MainWindow::on_pushButton_clicked()
{
    QString hostName = ui->lineEdit->text();
    quint16 hostPort = ui->lineEdit_2->text().toInt();
    QString userName = ui->lineEdit_3->text();
    QString userPasswd = ui->lineEdit_4->text();

    mqtt_client->setHostname(hostName);
    mqtt_client->setPort(hostPort);
    mqtt_client->setUsername(userName);
    mqtt_client->setPassword(userPasswd);

    if (mqtt_client->state() == QMqttClient::Disconnected)
    {
       mqtt_client->connectToHost();
       ui->pushButton->setText("断开");
    }
    else
    {
        mqtt_client->disconnectFromHost();
    }
}


/*更新连接状态*/
void MainWindow::updateLogStateChange()
{
    QString connect_state;
    if(mqtt_client->state() == QMqttClient::Connecting)
    {
        connect_state = "连接中...";
    }
    if(mqtt_client->state() == QMqttClient::Connected)
    {
        connect_state = "已连接";

        QMessageBox::information(this,tr("提示"), "已连接服务器");
    }
    else if(mqtt_client->state() == QMqttClient::Disconnected)
    {
       connect_state = "未连接";

       QMessageBox::information(this,tr("提示"), tr("mqtt服务器已断开!"));
    }

    ui->textBrowser->append(connect_state);

}


/*与代理断开连接*/
void MainWindow::brokerDisconnected()
{
    ui->pushButton->setText(tr("连接"));
    //ui->lw_subscription_topic->clear();         //清理连接
}

/*接收服务器发送来的消息的槽函数*/
void MainWindow::message_rcv(const QByteArray &message, const QMqttTopicName &topic)
{
    ui->textBrowser->append("收到主题: " + topic.name() + "\n内容:\n " + message);
}


void MainWindow::on_pushButton_2_clicked()
{
    QString topic = ui->lineEdit_5->text();
    if(topic == "")
    {
        QMessageBox::critical(this,tr("Error"),tr("请输入订阅主题"));
        return;
    }

    auto subscription = mqtt_client->subscribe(topic);
    if (!subscription)
    {
        QMessageBox::critical(this, QLatin1String("Error"), tr("订阅失败!请查看是否连接?"));
        return;
    }

    ui->textBrowser->append("已订阅:" + topic);
}

void MainWindow::on_pushButton_3_clicked()
{
    QString topic = ui->lineEdit_6->text();
    QString payload = ui->textEdit->toPlainText();
    quint8 Qos = static_cast(ui->spinBox->value());

    if(topic == "")
    {
        QMessageBox::critical(this, QLatin1String("Error"), tr("请输入发布的主题"));
        return;
    }
    if (mqtt_client->publish(topic,payload.toUtf8(),Qos) == -1)
    {
        QMessageBox::critical(this, QLatin1String("Error"), QString(" 发布消息失败!"));
        return;
    }
    else
    {
        ui->textBrowser->append("发布主题:"+ topic + "\n内容: \n" + payload);
    }


}
  • 实现效果

MQTT设计与实现_第6张图片

链接:https://pan.baidu.com/s/1ukITOBic5HPq75qTVZNfdw 
提取码:21l2

待更新:

在Linux中MQTT客户端(C)的设计与实现

在STM32中MQTT客户端(C)的设计与实现


 

参考文章:MQTT Broker 比较与选型——开源与商业服务器/服务对比 | MindSpark

               如何在Ubuntu 18.04上安装和保护Mosquitto MQTT Messaging Broker - 云+社区 - 腾讯云

MQTT协议从服务端到客户端详解 - 简书

程序源码:

你可能感兴趣的:(mqtt,物联网,linux,经验分享)