目录
前言
一、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(Message Queuing Telemetry Transport,消息队列遥测传输协议)是IBM开发的一个物联网通讯协议,OASIS(结构化信息标准促进组织)已宣布MQTT协议作为其新兴的物联网消息传递协议的首选。在MQTT的官方网站上,定义MQTT是一种machine-to-machine (M2M)设备之间通信的物联网互联协议,是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是MQTT服务器,消息发布者可以同时是订阅者。
在项目实际应用中,包含以下三个角色参与通信:
(1)MQTT代理服务程序,负责接收、存储、转发MQTT消息;
(2)MQTT客户端,一般情况下指的是手机APP程序,为用户提供一个交互界面,负责发布控制命令或查询设备状态信息等;
(3)后台服务端,指的是后台管理系统,负责与数据库交互,为网关和移动终端提供数据服务;实际上后台服务端在整个系统中也是一个MQTT客户端
MQTT传输的消息分为主题(Topic)和负载(payload)两部分:
Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)。
payload,可以理解为消息的内容,是指订阅者具体要使用的内容,对应我们的通信协议的消息包。
MQTT服务器以称为“消息代理”(Broker),可以是一个应用程序或一台设备。它是位于消息发布者和订阅者之间,它可以:
(1)接受来自客户的网络连接;
(2)接受客户发布的应用信息;
(3)处理来自客户端的订阅和退订请求;
(4)向订阅的客户转发应用程序消息。
物联网行业里可选的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 即MQTT 消息服务器,实际上是一个TCP服务端。在这里我们选择搭建轻量的mosquitto服务器作为演示,快速搭建一个MQTT Broker,使初步接触的同学们有一个快速的了解。我使用的是腾讯云的轻量型云服务器,系统版本为:Ubuntu 20.04
参考文章:https://blog.csdn.net/qq_28632173/article/details/84933527
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
Emqx broker是本次主要介绍的一款国产MQTT代理。按照网上所说,性能上EMQX单机版支持百万并发,集群支持千万并发,性能上完胜其他mqtt服务器。抛开性能不说,由于EMQX国产,支持国产,相对其他代理来说,EMQX具有丰富的中文文档资料,查阅方便,EMQX官网也有简洁的MQTT相关知识的介绍,对于初学者绝对是一个很好的选择。
这里先给出EMQX官方文档地址:产品概览 | EMQX 文档
安装部署的说明直接根据官方的文档引导进行即可。
连接、发布、订阅、服务质量、ping(后续会更新,其实也是一些知识点的搬运)
在Qt Creator中,默认是没有部署Mqtt模块的,在使用QT编程之前,先把mqtt 模块部署到开发环境当中。参考博文:Qt开发技术:mqtt介绍、QtMqtt编译和开发环境搭建_长沙红胖子-CSDN博客
下载
Qt官方在github上提供了源代码,地址:https://github.com/qt/qtmqtt.选择对应的版本下载
编译源码
打开源码文件,双击.pro打开工程文件,选择release编译。
编译这个源码需要安装perl,否则会报错:perl 不是内部或外部命令,也不是可运行的程序。
perl下载地址:https://www.perl.org/get.html
安装完Perl后会自动写入环境变量, 这时候再次编译(Release模式)QtMqtt源码,编译完成后得到以下文件:
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
#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
#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);
}
}
链接:https://pan.baidu.com/s/1ukITOBic5HPq75qTVZNfdw
提取码:21l2
参考文章:MQTT Broker 比较与选型——开源与商业服务器/服务对比 | MindSpark
如何在Ubuntu 18.04上安装和保护Mosquitto MQTT Messaging Broker - 云+社区 - 腾讯云
MQTT协议从服务端到客户端详解 - 简书
程序源码: