MQTT协议和相关概念介绍 && 搭建一个mosquitto的MQTT Broker

一、概述

对应MQTT协议的介绍与相关概念的阐述太多,本处简单叙述一下
MQTT,全称是 Message Queuing Telemetry Transport(消息队列遥测传输协议),是一种轻量级的发布/订阅消息传输协议
MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网(IoT)、小型设备、移动应用等方面有较广泛的应用
MQTT的特点:
开放消息协议,简单易实现
轻量级、占用带宽低(1字节固定报头,2字节心跳报文,最小化传输开销和协议交换,有效减少网络流量。)
发布/订阅模式,一对多消息发布;
基于TCP/IP网络连接,提供有序,无损,双向连接;
消息QoS(Quality of Service)支持,可靠传输保证(至多一次、最少一次、只有一次);
可传输任意类型的数据
收发消息都是异步的,发送方不需要等待接收方应答;

二、MQTT工作原理及术语

实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publisher)、代理(Broker)、订阅者(Subscriber)。其中,消息的发布者和订阅者都是客户端,消息代理(Broker)是服务器,消息发布者可以同时是订阅者。
MQTT 客户端:任何运行 MQTT 客户端库的应用或设备都是 MQTT 客户端,消息的发布者和订阅者都是客户端。例如,使用 MQTT 的即时通讯应用是客户端,使用 MQTT 上报数据的各种传感器是客户端,各种 MQTT 测试工具也是客户端。
MQTT 代理(Broker):MQTT Broker 是负责处理客户端请求的关键组件,包括建立连接、断开连接、订阅和取消订阅等操作,同时还负责消息的转发。一个高效强大的 MQTT Broker 能够轻松应对海量连接和百万级消息吞吐量,从而帮助物联网服务提供商专注于业务发展,快速构建可靠的 MQTT 应用。
发布/订阅模式:发布/订阅模式 与 客户端/服务器模式 的不同之处在于,它将发送消息的客户端(发布者)和接收消息的客户端(订阅者)进行了解耦。发布者和订阅者之间无需建立直接连接,而是通过 MQTT Broker 来负责消息的路由和分发。
1、发布方和订阅方都建立了到Broker的TCP连接;
2、订阅方告知Broker它要订阅的Topic;
3、发布方将消息发送到Broker,并指定消息主题(Topic);
4、Broker接收到消息后,检测哪些订阅方订阅了对应的Topic,然后将消息发送到订阅方;
5、订阅方从Broker获取消息;
6、如果某个订阅方处于离线状态,Broker可以先保存对应的消息,当订阅方下次连接到Broker的时候,再将之前的消息发送给订阅方;
订阅(Subscription):订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。
会话(Session):每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
主题(Topic):MQTT 消息的标签,MQTT 协议根据主题来转发消息。主题通过 / 来区分层级,类似于 URL 路径,例如:chat/room/1、sensor/+/temperature;
负载(Payload):消息订阅者所具体接收的内容。

MQTT主要是五种会话:连接请求、订阅、发布、心跳、断链。其中以连接请求较为复杂,这显然是容易理解的,比如连接过程中也会对用户名密码之类的判断,是否采用ssl加密等

三、MQTT的 QoS、Keepalive

mqtt还有很多其他的配置项,这里只重点介绍这两个,想详细了解其他的建议在有对应的需求的时候在仔细分析
QoS,全称是Quality of Service(服务质量)。
MQTT协议设计初衷是提供一套保证消息稳定传输的机制,用于网络带宽窄,信号不稳定环境下的数据传输,这套机制包括消息应答,存储和重传。在这套机制下提供了三种不同层次的QoS(Quality of Service),QoS是 发送方接收方 之间达成的协议,而不是Publisher和Subscriber之间达成的协议。
MQTT有以下三种QoS:
QoS 0:消息最多传送一次。如果当前客户端不可用,它将丢失这条消息。
QoS 1:消息至少传送一次。
QoS 2:消息只传送一次。
Keepalive
在实际使用MQTT协议时,无论是Broker还是Client都需要及时感知到MQTT是否断开;MQTT是基于TCP协议的应用层协议,理论上TCP断开时会通知上层应用,但是TCP协议有个半打开连接的问题,这种状态下,一段的TCP连接已经失效,但是另一端并不知情,需要很长时间才能感知到对端连接已经断开;因此仅仅依赖TCP层的连接状态监测是不够的,于是MQTT设计了一套keepalive机制,
MQTT协议约定:在1.5*keepalive时间间隔内,如果Broker没有收到来自Client的任何数据包,Broker就认为和Client的连接断开;同理Client在这个时间间隔内没有收到Broker的任何数据包,Client也会认为他和Broker的连接断开;
MQTT协议设计了一对PINGREQ/PINGRESP数据包,当Broker和Client之间没有任何数据交互时,可以通过这对数据包满足keepalive的约定和网络状态监测;
keepalive特性
如果一个Keepalive时间间隔内,Client和Broker有过数据包传输,Client就没有必要再使用PINGREQ数据包了;
Keepalive的值时有Client在发送CONNECT数据包时指定,不同的Client可以指定不同的值;
Keepalive的最大值是18时12分15秒
Keepalive 的值为0时代表不使用Keepalive机制

四、MQTT的工具和开源库

这里介绍如何使用 mosquitto 搭建一个 MQTT Broker。
4.1、 MQTT Broker 相关工具、开源库
MQTT Broker 是MQTT协议的核心,主要作用是接收发布者的消息,然后转发给对应的订阅者。Broker可以对Clinet接入进行授权,并对Client进行权限控制。
Eclipse Mosquitto是一个开源(EPL/EDL许可)消息代理,它实现了MQTT协议版本 5.0、3.1.1和3.1。Mosquito 是轻量级的,适用于从低功耗单板计算机到全服务器的所有设备。
Mosquitto项目还提供了一个用于实现MQTT客户端的C库,以及非常流行的Mosquitto_pub和Mosquitto_sub命令行MQTT客户端。

Mosquito是Eclipse基金会的一部分,是iot.Eclipse.org项目。开发由Cedalo推动。
关于 的更多内容,可以参考其官网:https://mosquitto.org/

4.2、mosquitto 的下载
mosquitto 的下载地址:https://mosquitto.org/download/
如果不想下载最新版本的,也可以在其GitHub路径去下载:https://github.com/eclipse/mosquitto/tags
本文是直接在官网下载的,下载的版本是:mosquitto-2.0.18.tar.gz。

4.3、mosquitto 的交叉编译
在编译 mosquitto-2.0.18.tar.gz 之前需要先编译其依赖库:openssl库 和 cJSON库。
4.3.1 openssl
下载地址:https://codeload.github.com/openssl/openssl/tar.gz/refs/tags/OpenSSL_1_1_1g
为什么使用这么旧的版本,因为这个我之前编译过,而且使用没问题。
编译步骤:
解压缩

tar zxf openssl-OpenSSL_1_1_1g.tar.gz

进入目录,并配置输出目录和交叉编译器, (我是直接在我的虚拟机上编译的,所以直接选择gcc,个别文章加了这个选项就不用去掉 -m64,我这里行不通)

cd openssl-OpenSSL_1_1_1g/
./config no-asm shared no-async --prefix=`pwd`/ssl_result  --cross-compile-prefix=gcc

执行下面命令,删除Makefile文件的 -m64(如果确认自己的系统是32位的,可以不用操作,正常顺手操作即可)

sed -i 's/-m64//' Makefile

编译、安装

make && make install

成功编译后,在openssl-OpenSSL_1_1_1g/目录会生成一个ssl_result目录,可以看到里面生成的库:在这里插入图片描述
4.3.2 cJSON 的下载、交叉编译
cJSON库是C语言编写的,用来解析JSON信息的库,本文使用的是 cJSON-1.7.17.tar.gz。
下载地址:https://codeload.github.com/DaveGamble/cJSON/tar.gz/refs/tags/v1.7.17
解压缩

tar xvf xxxxx.tar.gz

修改安装目录路径

sed -i '/PREFIX ?= /a PREFIX ?= $(PWD)\/..\/${CJSON_VER}_result' Makefile
sed -i '/PREFIX ?= \/usr\/local/d' Makefile

编译安装

make CC=gcc
make install

成功编译后,在cJSON-1.7.17/目录会生成一个cjson_result目录,可以看到里面生成的库:
在这里插入图片描述

4.3.3 mosquitto 交叉编译
编译步骤:
1、解压缩、进入源码目录:

tar xvf mosquitto-2.0.18.tar.gz
cd mosquitto-2.0.18

2、修改config.mk,指定依赖库的头文件路径、库路径,并且指定安装目。
在 config.mk 的 146 行,在ARCH:=$(shell uname -p)语句下面添加下面语句:

# 指定ssl库、cJSON库的头文件和库文件
CFLAGS += -I/home/mqtt/openssl-OpenSSL/ssl_result/include -I/home/mqtt/cJSON-1.7.17/cjson_result/include
LDFLAGS+= -L/home/mqtt/openssl-OpenSSL/ssl_result/lib -L/home/mqtt/cJSON-1.7.17/cjson_result/lib -lssl -lcrypto -dl

# 指定安装目录
DESTDIR=/home/mqtt/mosquitto-2.0.18/mosquitto-2.0.18_result

这里的路径设置成自己的路径
注意:
没有添加上面语句,或者添加不正确的话,会报编译错误:…/config.h:61:12: fatal error: openssl/opensslconf.h: No such file or directory
1、指定的路径必须使用绝对路径,因为这个config.mk可能被其他层级的Makefile包括,不使用绝对路径会出错。
2、-lssl -lcrypto -dl这几个选项也要加,不然链接时可能会报错。
3、DESTDIR目录一定要指定,不然make install的时候可能会安装到根目录去。

3、编译、安装

make CC=gcc CXX=g++
make install

编译完成后,可以看到如下的几个文件:
在这里插入图片描述

五、mosquitto 的使用教程

我是在Ubuntu发行版系统部署的环境,如果要部署到嵌入式开发板也可以参考,主要就是让依赖的动态库可以被找到。

5.1运行 mosquitto (MQTT Broker)

在编译结果目录mosquitto-2.0.18_result/usr/local/sbin 中存在 mosquitto 的可执行文件,mosquitto 就是我们要部署的 MQTT Broker(MQTT 代理),将它运行之后,我们就可以通过MQTT客户端进行发布/订阅
首先,设置 LD_LIBRARY_PATH 环境变量,添加依赖库的路径,执行如下语句(路径根据你自己的设置):

export LD_LIBRARY_PATH=/nfsroot/mosquitto_result_dir/ssl_result/lib:$LD_LIBRARY_PATH

然后,添加 mosquitto 配置文件,在当前目录新建文件mosquitto.conf,内容如下:

# 设置 Mosquitto 服务器监听所有网络接口(通过 0.0.0.0 指定)上的 1883 端口.
listener 1883 0.0.0.0

# 设置运行 Mosquitto 服务时使用的用户为 root。
# 这通常不是一个安全的做法,因为通常推荐使用一个权限较低的专用用户账户来运行服务以减少潜在的安全风险。
user root

# 允许匿名用户连接到 MQTT 服务器。这意味着客户端无需提供用户名和密码即可连接并发布、订阅消息。
allow_anonymous true

# 指定 Mosquitto 服务运行时生成的进程 ID 文件位置。
#pid_file /var/run/mosquitto.pid

# 开启持久化模式,意味着在服务器重启后,一些会话状态和保留消息等信息会被保存下来
#persistence true

# 设置持久化数据存储的位置,在本例中是 /tmp 目录。
#persistence_location /tmp

# 指定日志输出方式为写入文件,并设定日志文件的路径为 /tmp/mosquitto.log。
#log_dest file /tmp/mosquitto.log

# 指定包含其他配置文件的目录,当 Mosquitto 启动时,会加载此目录下的所有 .conf 结尾的文件作为附加配置。
#include_dir /mnt/mosquitto/etc/mosquitto/conf.d/

#max_inflight_messages 1024
#max_queued_messages 5120
#message_size_limit 2048000
#set_tcp_nodelay true

最后,运行 mosquitto,-c 是指定 mosquitto 配置文件mosquitto.conf 的路径(根据自己的设置):

./mosquitto -c /home/mqtt/mosquitto-2.0.18/mosquitto-2.0.18_result/usr/local/sbin/mosquitto.conf 

在这里插入图片描述
5.2 运行 mosquitto_sub 订阅消息
mosquitto_sub 是一个MQTT客户端,实现了订阅消息的功能,使用步骤:
1、在嵌入式开发板重新打开一个命令行窗口,设置 LD_LIBRARY_PATH 环境变量,具体路径看自己的目录:

export LD_LIBRARY_PATH=/nfsroot/mosquitto_result_dir/mosquitto-2.0.18_result/usr/local/lib/:/nfsroot/mosquitto_result_dir/cJSON-1.7.17_result/lib/:$LD_LIBRARY_PATH

2、运行 mosquitto_sub 订阅消息,命令如下,-v表示打印发布的消息,-h 指定运行MQTT Broker的主机ip,-t 指定要订阅的主题(Topic):

./mosquitto_sub -v -h 192.168.3.227 -t /server/fromArm

5.3 运行 mosquitto_pub 发布消息
mosquitto_pub 也是一个MQTT客户端,实现了发布消息的功能,使用步骤:

1、在嵌入式开发板重新打开一个命令行窗口,设置 LD_LIBRARY_PATH 环境变量,具体路径看自己的目录:

./mosquitto_pub -h 192.168.3.227 -t /server/fromArm -m {"data":"hello world!"}

2、运行 mosquitto_pub 发布消息,命令如下,-h 指定运行MQTT Broker的主机ip,-t 指定要发布的主题(Topic),-m 指定要发送的消息:

./mosquitto_pub -h 192.168.3.227 -t /server/fromArm -m {"data":"hello world!"}

六、使用MQTT客户端例子源码通信

此列使用同步库接口MQTTClient_subscribe.c
libpaho-mqtt3a.so:异步的paho.mqtt.c库
libpaho-mqtt3as.so:异步的、使用了SSL的paho.mqtt.c库
libpaho-mqtt3c.so:经典的、同步的paho.mqtt.c库
libpaho-mqtt3cs.so:经典的、同步的、使用了SSL的paho.mqtt.c库
异步和同步库的区别(Asynchronous vs synchronous client applications)
下面是译文,原文地址:Asynchronous vs synchronous client applications

客户端库支持两种操作模式。这些模式被称为同步模式和异步模式。如果您的应用程序调用 MQTTClient_setCallbacks(),这将使客户端进入异步模式,否则它将以同步模式运行。

在同步模式下,客户端应用程序在单个线程上运行。消息是使用 MQTTClient_publish() 和 MQTTClient_publishMessage() 函数发布的。要确定QoS1或QoS2(请参阅Quality of service)消息已成功传递,应用程序必须调用 MQTTClient_waitForCompletion() 函数。Synchronous publication example 中展示了一个同步发布的示例。在同步模式下接收消息使用MQTTClient_receive()函数。客户端应用程序必须相对频繁地调用MQTTClient_ereceived()或MQTTClient_yield(),以便允许(执行)确认和保持与服务器的网络连接的 MQTT “ping” 操作。

在异步模式下,客户端应用程序在多个线程上运行。主程序像同步模式一样地调用客户端库中的函数来发布和订阅。然而,握手和保持网络连接的处理是在后台执行的。通过调用 MQTTClient_setCallbacks(),使用在库中注册的回调将状态和消息接收通知提供给客户端应用程序(请参阅 MQTTClient_messageArrived()、MQTTClient_connectionLost() 和MQTTClient_edeliveryComplete() )。然而,这个API不是线程安全的——在没有同步的情况下,不可能从多个线程调用它。您可以使用MQTTAsync API来实现这一点。

6.1 paho.mqtt.c 库的下载、交叉编译
本文下载的是paho.mqtt.c-1.3.13.tar.gz,下载地址:https://github.com/eclipse/paho.mqtt.c/archive/refs/tags/v1.3.13.tar.gz
编译 paho.mqtt.c 库之前,需要先编译其依赖库:openssl。

6.2 paho.mqtt.c 交叉编译
1、解压缩,创建要安装目录paho.mqtt.c_result

tar xvf paho.mqtt.c-1.3.13.tar.gz

mkdir paho.mqtt.c_result/bin -p
mkdir paho.mqtt.c_result/include -p
mkdir paho.mqtt.c_result/lib -p
mkdir paho.mqtt.c_result/share/man/man1 -p

2、进入目录,交叉编译

cd paho.mqtt.c-1.3.13/
make  CC=gcc:="-I `pwd`/../ssl_result/include" LDFLAGS:="-L `pwd`/../ssl_result/lib"

CFLAGS:=“-I pwd/…/ssl_result/include”:指定前面编译的 openssl 的头文件;
LDFLAGS:=“-L pwd/…/ssl_result/lib”:指定前面编译的 openssl 的库文件路径;

3、make install,安装编译结果

make install prefix=`pwd`/../paho.mqtt.c_result 

prefix=pwd/…/paho.mqtt.c_result :指定安装目录路径;

6.3 paho.mqtt.c库写一个MQTT客户端
下面提供两个使用了 paho.mqtt.c库(libpaho-mqtt3a) MQTT客户端的例子。
订阅——MQTTClient_subscribe.c
这是使用了 libpaho-mqtt3a.so 进行订阅消息的源码,源码路径在源码的这个路径:src/samples/MQTTClient_subscribe.c,只更改了服务器地址。完整代码如下:

/*******************************************************************************
 * Copyright (c) 2012, 2023 IBM Corp., Ian Craggs
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * and Eclipse Distribution License v1.0 which accompany this distribution. 
 *
 * The Eclipse Public License is available at 
 *   https://www.eclipse.org/legal/epl-2.0/
 * and the Eclipse Distribution License is available at 
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Ian Craggs - initial contribution
 *******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"

//#define ADDRESS     "tcp://mqtt.eclipseprojects.io:1883"
#define ADDRESS     "改成自己电脑的ip"
#define CLIENTID    "ExampleClientSub"
#define TOPIC       "MQTT Examples"
#define PAYLOAD     "Hello World!"
#define QOS         1
#define TIMEOUT     10000L

volatile MQTTClient_deliveryToken deliveredtoken;

void delivered(void *context, MQTTClient_deliveryToken dt)
{
    printf("Message with token value %d delivery confirmed\n", dt);
    deliveredtoken = dt;
}

int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
    printf("Message arrived\n");
    printf("     topic: %s\n", topicName);
    printf("   message: %.*s\n", message->payloadlen, (char*)message->payload);
    MQTTClient_freeMessage(&message);
    MQTTClient_free(topicName);
    return 1;
}

void connlost(void *context, char *cause)
{
    printf("\nConnection lost\n");
    if (cause)
    	printf("     cause: %s\n", cause);
}

int main(int argc, char* argv[])
{
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    int rc;
 
    if ((rc = MQTTClient_create(&client, ADDRESS, CLIENTID,
        MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to create client, return code %d\n", rc);
        rc = EXIT_FAILURE;
        goto exit;
    }

    if ((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to set callbacks, return code %d\n", rc);
        rc = EXIT_FAILURE;
        goto destroy_exit;
    }

  .........................................................................
exit:
    return rc;
}

编译

gcc src/samples/MQTTClient_subscribe.c -I paho.mqtt.c_result/include/ -L paho.mqtt.c_result/lib/ -lpaho-mqtt3a  -lpaho-mqtt3c -o MQTTClient_subscribe

6.4 发布——MQTTClient_publish.c
这是使用了 libpaho-mqtt3a.so 进行发布消息的源码,源码路径在源码的这个路径:src/samples/MQTTClient_publish.c,只更改了服务器地址。完整代码如下:略
编译如上,注意相关库的路径

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值