引用:【正点原子】1.MX6U嵌入式Linux C应用编程指南V1.4.pdf 著作权归作者所有,装载请注明出处。
一、MQTT 简介
《MQTT 协议规范中文版》一书中对 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)进行了描述:MQTT 是一种基于客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。----MQTT 协议中文版
以上这段话很好的描述了 MQTT 的全部含义,它是一种轻巧、开放、简单、规范的网络通信协议。与HTTP 协议一样,MQTT 协议也是应用层协议,工作在 TCP/IP 四层模型中的最上层(应用层),构建于 TCP/IP协议上。MQTT 最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
如今,MQTT 成为了最受欢迎的物联网协议,已广泛应用于车联网、智能家居、即时聊天应用和工业互联网等领域。目前通过 MQTT 协议连接的设备已经过亿,这些都得益于 MQTT 协议为设备提供了稳定、可靠、易用的通信基础。
1、MQTT 的主要特性
MQTT 协议是为工作在低带宽、不可靠网络的远程传感器和控制设备之间的通讯而设计的协议,它具有以下主要的几项特性:
①、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
②、基于 TCP/IP 提供网络连接。主流的 MQTT 是基于 TCP 连接进行数据推送的,但是同样也有基于 UDP 的版本,叫做 MQTT-SN。这两种版本由于基于不同的连接方式,优缺点自然也就各有不同了。
③、支持 QoS 服务质量等级。根据消息的重要性不同设置不同的服务质量等级。
④、小型传输,开销很小,协议交换最小化,以降低网络流量。这就是为什么在介绍里说它非常适合"在物联网领域,传感器与服务器的通信,信息的收集",要知道嵌入式设备的运算能力和带宽都相对薄弱,使用这种协议来 传递消息再适合不过了,在手机移动应用方面,MQTT 是一种不错的 Android 消息推送方案。
⑤、使用 will 遗嘱机制来通知客户端异常断线。
⑥、基于主题发布/订阅消息,对负载内容屏蔽的消息传输。
⑦、支持心跳机制。
以上便是 MQTT 的主要特性了。
2、MQTT 历史
MQTT 协议最初版本是在 1999 年建立的,该协议的发明人是的 Andy Stanford-Clark 和 Arlen Nipper。
MQTT 最初是用于石油管道的传感器与卫星之间数据传输。他们当时正在开发一个利用卫星通讯监控输油管道的项目,为了实现这个项目要求,他们需要开发一种用于嵌入式设备的通讯协议,这种通讯协议必须满足以下条 件:
⚫ 易于实现,服务器必须要实现成千上万个客户端的接入
⚫ 数据传输的服务质量可控,根据数据的重要性和特性,设置不同等级的服务质量
⚫占用带宽小,单次数据量小,但不能出错
⚫ 必须能够适应高延迟、掉线、断网等网络通信不可靠的风险
⚫ 设备连接状态可知,云端与设备端保持长连接
通过以上几个条件可知:
⚫ MQTT 服务器可以连接大量的远程传感器和控制设备,与远程客户端保持长连接,具有一定的实时性。
⚫ 云端向设备端发送消息,设备端可以在最短的时间内接收到并作出回应。
⚫ MQTT 更适合需要实时控制的场合,尤其适合执行器。
⚫ 云端与客户端需要保持长连接,要能够获取到设备的连接状态,就需要时不时地发送心跳包,这就不会省电,所以,MQTT 并不适合低功耗场合。从以上几点不难看出,MQTT 从诞生之初就是专为低带宽、高延迟或不可 靠的网络而设计的。虽然历经几十年的更新和变化,以上这些特点仍然是 MQTT 协议的核心特点。但是与最初不同的是,MQTT 协议已经从嵌入式系统应用拓展到开放的物联网(IoT)领域。
3、MQTT 版本
目前 MQTT 主流版本有两个,分别是 MQTT3.1.1 和 MQTT5。MQTT3.1.1 是在 2014 年 10 月发布的,而 MQTT5 是在 2019 年 3 月发布的。虽然 MQTT3.1.1 与 MQTT5 在时间相差了将近五年,但是 MQTT3.1.1作为一 个经典的版本,目前仍然是主流版本,能够满足大部分实际需求。MQTT5 是在 MQTT3.1.1 的基础上进行了升级,因此 MQTT5 是完全兼容 MQTT3.1.1 的。而 MQTT5 是在 MQTT3.1.1 的基础上添加了更多的功能、补充完善 MQTT 协议。具体增加了哪些功能,这里不作说明,如果大家有兴趣可以自行百度了解。
二、MQTT 协议(上)
在上小节中,我们了解了 MQTT 的背景知识和基本特点,本小节开始我们将一起了解 MQTT 通信基本原理。
MQTT 通信基本原理
MQTT 是一种基于客户端-服务端架构的消息传输协议,所以在 MQTT 协议通信中,有两个最为重要的角色,它们便是服务端和客户端。
1、服务端
MQTT 服务端通常是一台服务器(broker),它是 MQTT 信息传输的枢纽,负责将 MQTT 客户端发送来的信息传递给 MQTT 客户端;MQTT 服务端还负责管理 MQTT 客户端,以确保客户端之间的通讯顺畅,保证 MQTT信息得以正确接收和准确投递。
2、客户端
MQTT 客户端可以向服务端发布信息,也可以从服务端收取信息;我们把客户端发送信息的行为称为“发布”信息。而客户端要想从服务端收取信息,则首先要向服务端“订阅”信息。“订阅”信息这一操作很像我们在使用微信 时“关注”了某个公众号,当公众号的作者发布新的文章时,微信官方会向关注了该公众号的所有用户发送信息,告诉他们有新文章更新了,以便用户查看。
3、MQTT 主题
上面我们讲到了,客户端想要从服务器获取信息,首先需要订阅信息,那客户端如何订阅信息呢?这里我们要引入“主题(Topic)”的概念,“主题”在 MQTT 通信中是一个非常重要的概念,客户端发布信息以及订阅信息都是围绕“主题”来进行的,并且 MQTT 服务端在管理 MQTT 信息时,也是使用“主题”来控制的。
客户端发布消息时需要为消息指定一个“主题”,表示将消息发布到该主题;而对于订阅消息的客户端来说,可通过订阅“主题”来订阅消息,这样当其它客户端或自己(当前客户端)向该主题发布消息时,MQTT 服务端就会 将该主题的信息发送给该主题的订阅者(客户端)。
为了便于您更好理解服务端是如何通过“主题”来控制客户端之间的信息通讯,我们来看看下图实例:
图:MQTT 通信示例1
在以上图示中一共有三个 MQTT 客户端,它们分别是开发板、手机和电脑。MQTT 服务端在管理 MQTT通信时使用了“主题”来对信息进行管理。比如上图所示,假设我们需要利用手机和电脑获取开发板在运行过程中SoC 芯片的温度,那么首先电脑和手机这两个客户端需要向 MQTT 服务器订阅主题“芯片温度”;接下来,当开发板客户端向服务端的“芯片温度”主题发布信息(假设信息的内容就是当前的温度值)后,服务端就会首先检查 都有哪 些客户端订阅了“芯片温度”这一主题的信息,而当它发现订阅了该主题的客户端有一个手机和一个电脑,于是服务端就会将刚刚收到的“芯片温度”信息转发给订阅了该主题的手机和电脑客户端。
通过以上的这种实例,手机和电脑便可以获取到开发板运行时 SoC 芯片的温度值。
以上实例中,开发板是“芯片温度”主题的发布者,而手机和电脑则是该主题的订阅者。
值得注意的是,MQTT 客户端在通信时,角色往往不是单一的,一个客户端既可以作为信息发布者也可以同时作为信息订阅者。如下图所示:
图:MQTT 通信示例2
上图中的所有客户端都是围绕“LED 控制”这一主题进行通信。此时,对于“LED 控制”这一主题来说,手机和电脑客户端成为了 MQTT 信息的发布者而开发板则成为了 MQTT 信息的订阅者(接收者)。
所以由此可知,针对不同的主题,MQTT 客户端可以切换自己的角色,它们可能对主题 A 来说是信息发布者,但是对于主题 B 就成了信息订阅者,所以一个 MQTT 客户端它的角色并不是固定的,所以大家一定要理解“主 题”这个概念。
4、MQTT 发布/订阅特性
1 、客户端相互独立;
2、空间上分离;
3、时间上可异步:MQTT 客户端在发送和接收信息时无需同步。这一特点对物联网设备尤为重要,前面我们也介绍了,MQTT 从诞生之初就是专为低带宽、高延迟或不可靠的网络而设计的,高延迟和不可靠网络必然就会导致时间上的异步;物联网设备在运行过程中发生意外掉线是非常正常的情况,我们使用上面的实例二的场景来作说明,当开发板在运行过程中,可能会由于突然断电(假设开发板是通过电源适配器供电的)导致掉线,这时开发板会断开与 MQTT 服务端的连接。假设此时我们的手机客户端向开发板客户端所订阅的“LED 控制”主题发布了信息,而开发板恰恰不在线,这时,MQTT 服务端可以将“LED 控制”主题的新信息保存,待开发板客户端再次上
线后,服务端再将“LED 控制”信息推送给开发板。所以这就必然导致了,手机发送信息与开发板接收信息在时间上是异步的。
三、连接 MQTT 服务端
MQTT 客户端连接服务端总共包含了两个步骤:
①、首先客户端需要向服务端发送连接请求,这个连接请求实际上就是向服务端发送一个 CONNECT报文。
②、MQTT 服务端收到连接请求后,会向客户端发送连接确认。连接确认实际上是向客户端发送一个CONNACK 报文。
CONNECT 报文
如果此 CONNECT 报文的格式或内容不符合 MQTT 规范,则服务器会拒绝客户端的连接请求。
图 :CONNECT 报文的内容
MQTT 报文组成分为三个部分:固定头(Fixed header)、可变头(Variableheader)以及有效载荷(Payload,消息体)。
1、固定头(Fixed header):存在于所有 MQTT 报文中,固定头中有报文类型标识,可用于识别是哪种 MQTT 报文,譬如该报文是 CONNECT 报文还是 CONNACK 报文,亦或是其它类型报文。
2、可变头(Variable header):存在于部分类型的 MQTT 报文中,报文的类型决定了可变头是否存在及其具体的内容。
3、 消息体(Payload):存在于部分类型的 MQTT 报文中,payload 就是消息载体的意思。
clientId--客户端 id
clientId 是 MQTT 客户端的标识,也就是 MQTT 客户端的名字,MQTT 服务端可通过 clientId 来区分不同的客户端,MQTT 服务端用该标识来识别客户端。因此 clientId 必须是独立的,如果两个 MQTT 客户端使用相同 clientId 标识,服务端会把它们当成同一个客户端来处理。通常 clientId 是由一串字符所构成的。
keepAlive--心跳时间间隔
cleanSession--清除会话
它是一个布尔值,如果连接服务端时 cleanSession=0,当 MQTT 客户端由离线(与服务端断开连接)再次上线时,离线期间发给客户端的所有 QoS>0 的消息仍然可以接收到;如果连接服务端时 cleanSession=1,当 MQTT 客户端由离线(与服务端断开连接)再次上线时,离线期间发给客户端的所有消息一律接收不到。
CONNACK 报文
图: CONNACK 报文的内容
CONNACK 报文包括两个信息,一个是 returnCode(连接返回码),另一个是 sessionPresent。
returnCode--连接返回码:用来说明连接状态。
表:返回码说明
sessionPresent
sessionPresent 与 CONNECT 报文中的 cleanSession 有关系,前面说了,客户端连接服务端时,如果cleanSession=0,服务端会保存与客户端的会话状态,记录客户端订阅的主题,即使客户端断开了与服务端的连接,会话任然保持并保存离线消息,直到客户端重新上线,这些离线消息就会发给客户端。
在 cleanSession=0 的情况下,当客户端连接到服务器之后,可通过 CONNACK 报文中返回的sessionPresent 来查询服务端是否为客户端保存了会话状态(客户端上一次连接时的会话状态信息),如果服务端已为客户端保存了上一次连接时的会话状态,则 sessionPresent=1,如果没有保存会话状态,则sessionPresent=0。
如果 cleanSession=1,在这种情况下,客户端是不需要服务端保存会话状态的,那么服务端发送的确认连接 CONNACK 报文中,sessionPresent 肯定是 false(sessionPresent=0),也就是说,服务端没有保存客户端的会话状态信息。
四、断开连接
当 MQTT 客户端连接到服务端之后,在后续的通信过程中,如果客户端想要断开与服务端的连接,此时客户端可以主动向服务端发送一个 DISCONNECT 报文来断开与服务端的连接。
五、发布消息、订阅主题与取消订阅主题
MQTT 客户端向服务端发布消息其实就是向服务端发送一个 PUBLISH 报文,服务端收到客户端发送过来的 PUBLISH 报文之后,会向发送发回复一个报文。根据 QoS 的不同,回复的报文类型也是不同的,并且整个发布消息的过程也将会有所区别;譬如对于 QoS=1 时,客户端向服务端发送 PUBLISH 报文,服务端收到 PUBLISH 报文之后会向发送方回复 PUBACK 报文;而对于 QoS=2 的情况,将会更加复杂,这个后续再给大家介绍。
图:PUBLISH 报文的内容
packetId--报文标识符
报文标识符可用于对 MQTT 报文进行标识(识别不同的报文)。不同的 MQTT 报文所拥有的标识符不同。MQTT 设备可以通过该标识符对 MQTT 报文进行甄别和管理,MQTT 协议内部使用的标识符。请注意:报文标识符的内容与 QoS 级别有密不可分的关系。只有 QoS 级别大于 0 时,报文标识符才是非零数值。如果 QoS 等于 0,报文标识符为 0,为什么是这样的呢?后面向大家介绍 QoS 概念时会做一个简单地说明!
topicName--主题名字
这个就是发布消息时对应的主题的名字,这是一个字符串,譬如上图中 topicName=“myTopic”,表示会将消息发布到“myTopic”这个主题。
payload--有效载荷
有效载荷是我们希望通过 MQTT 所发送的实际内容。我们可以使用 MQTT 协议发送字符串文本,图像等格式的内容。这些内容都是通过有效载荷所发送的。
qos--服务质量等级
QoS(Quality of Service)表示 MQTT 消息的服务质量等级。QoS 有三个级别:0、1 和 2,QoS 决定MQTT 通信有什么样的服务保证。有关 QoS 的详细信息我们会在后续内容中详细讲解。
retain--保留标志
在默认情况下,当客户端订阅了某一主题后,并不会马上接收到该主题的信息。因为客户端订阅该主题之后,并没有其它客户端向该主题发布消息;只有在客户端订阅该主题后,服务端接收到该主题的新消息时,服务端才会将最新接收到的该主题消息推送给客户端。但是在有些情况下,我们需要客户端在订阅了某一主题后马上接收到一条该主题的信息。这时候就需要用到保留标志这一信息。关于保留标志的具体使用方法,我们将在本教程的后续部分进行详细讲解。
dup--重发标志
dup 标志指示此消息是否重复。当 MQTT 报文的接收方没有及时向报文发送发回复确认收到报文时,发送方会以为对方没有收到信息,会再次重复发送 MQTT 报文(譬如客户端向服务端发送 PUBLISH 报文,服务端收到 PUBLISH 报文之后需要向客户端回复一个 PUBACK 报文,如果客户端没收到 PUBACK 报文,则会认为服务端可能没接收到自己发送的报文,将会再次发送 PUBLISH 报文)。在重复发送 MQTT 报文时,发送方会将此“dup--重发标志”设置为 true。请注意,重发标志只在 QoS 级别大于 0 时使用。有关 QoS 的详细信息,我们将会在后续内容中为您做详细介绍。
2、SUBSCRIBE--订阅主题
客户端要想订阅主题,首先要向服务端发送主题订阅请求。客户端是通过向服务端发送 SUBSCRIBE 报文来实现这一请求的。该报文包含有一系列“订阅主题名”。请留意,一个 SUBSCRIBE 报文可以包含有单个或者多个订阅主题名。也就是说,一个 SUBSCRIBE 报文可以用于订阅一个或者多个主题。
在以上 PUBLISH 报文讲解中,我们曾经提到过 QoS(服务质量等级)这一概念。同样的,客户端在订阅主题时也可以明确 QoS。服务端会根据 SUBSCRIBE 中的 QoS 来提供相应的服务保证。
另外每一个 SUBSCRIBE 报文还包含有“报文标识符”。报文标识符可用于对 MQTT 报文进行标识。不同的 MQTT 报文所拥有的标识符不同。MQTT 设备可以通过该标识符对 MQTT 报文进行甄别和管理。
当客户端向服务端发送 SUBSCRIBE 报文,服务端接收到 SUBSCRIBE 报文之后会向客户端回复一个SUBACK 报文(订阅确认报文),如下图所示:
图 :客户端订阅主题
服务端接收到客户端的订阅报文后,会向客户端发送 SUBACK 报文确认订阅。
SUBACK 报文包含有“订阅返回码”和“报文标识符”这两个信息。 、
returnCode--订阅返回码
表:返回码说明
3、UNSUBSCRIBE--取消订阅主题
客户端订阅了某一主题之后,可以随时取消订阅,MQTT 协议提供了这样的操作。
客户端通过向服务端发送一个 UNSUBSCRIBE 报文来取消订阅主题,当服务端接收到 UNSUBSCRIBE报文后,会向发送发回复一个 UNSUBACK 报文(取消订阅确认报文),如下图所示:
图 :客户端取消订阅主题
UNSUBSCRIBE 报文包含两个重要信息,第一个是取消订阅的主题名称,同一个 UNSUBSCRIBE 报文可以同时包含多个取消订阅的主题名称。另外,UNSUBSCRIBE 报文也包含“报文标识符”,MQTT 设备可以通过该标识符对报文进行管理。
当服务端接收到 UNSUBSCRIBE 报文后,会向客户端发送取消订阅确认报文 –UNSUBACK 报文。该报文含有客户端所发送的“取消订阅报文标识符”。
客户端接收到 UNSUBACK 报文后就可以确认取消主题订阅已经成功完成了。
六、主题的进阶知识
1、主题的基本形式
(1)、主题是区分大小写的。所以"LEDControl"和"ledControl"是两个不同的主题。
(2)、 主题可以使用空格。譬如"LED Control",虽然主题允许使用空格,但是笔者建议大家尽量不要使用空格。
(3)、不要使用中文主题。虽然有些 MQTT 服务器支持中文主题,但是绝大部分 MQTT 服务器是不支持中文主题的,所以大家不要使用中文主题,而是使用 ASCII 字符来作为 MQTT 主题。
2、主题分级
支持主题分级,各个级别之间使用" / "符号进行分隔——"home/sensor/led/brightness"。
分别是第 1 级 home、第 2 级 sensor、第三级 led、第 4 级 brightness。主题的每一级至少需要一个字符;
· 3、主题通配符
当客户端订阅主题时,可以使用通配符同时订阅多个主题。通配符只能在订阅主题时使用,下面我们将介绍两种通配符:单级通配符和多级通配符。
单级通配符:+
单级通配符可以匹配任意一个主题级别,注意是一个主题级别,譬如示例如下:
"home/sensor/+/status"
当客户端订阅了上述主题之后,将会收到以下主题的信息内容:
"home/sensor/led/status"
"home/sensor/key/status"
"home/sensor/beeper/status"
多级通配符:#
多级通配符自然是可以匹配任意数量个主题级别,而不再是单一主题级别,多级通配符使用“#”号来表示,譬如:"home/sensor/#"
当客户端订阅了上面这个主题之后,便可以收到如下注意的信息:
"home/sensor/led"
"home/sensor/led/brightness"
"home/sensor/beeper/status"
4、主题应用注意事项
(1)、以$开头的主题
以$号开头的主题是 MQTT 服务端系统保留的特殊主题,客户端不可随意订阅或向其发布信息,譬如:
"$SYS/monitor/Clients"
"$SYS/monitor/+"
"$SYS/#"
(2)、不要使用“/”作为主题开头
(3)、主题中不要使用空格
(5)、主题中尽量使用 ASCII 字符
七、下载、安装 MQTT.fx 客户端软件
想要将电脑作为 MQTT 客户端,我们需要在电脑上安装一个 MQTT 客户端软件,MQTT 客户端软件很多,这里笔者推荐 MQTT.fx 这款软件,这款软件是目前主流的 MQTT 桌面客户端,它支持 Windows、Mac、Linux 等多种操作系统,它的官网是 http://mqttfx.jensd.de/。进入到官网下载 MQTT.fx 软件,如下所示:
图 : MQTT.fx 下载地址
以 1.7.1 版本为例,进入到 http://www.jensd.de/apps/mqttfx/1.7.1/链接地址进行下载,如下:
下载完成之后得到一个 exe 安装包文件,运行后如下所示:
也可使用MQTTBox
八、MQTT 服务端
MQTT 客户端搞定之后,接下来就是 MQTT 服务端的问题了。我们可以自己搭建一个 MQTT 服务器,也可以使用现成的 MQTT 服务器。
1、自己搭建 MQTT 服务器
如果你没有公网 IP 的主机,搭建的服务器也只能在局域网内使用,外部网络无法接入。
家简单地提一下关于自己如何去搭建 MQTT 的服务器。MQTT 服务器非常多,如 apache 的 ActiveMQ、emtqqd、HiveMQ、Emitter、Mosquitto、Moquette 等等,既有开源的服务器也有商业服务器;作为学习,我们推荐 Mosquitto,Mosquitto 是一个高质量轻量级的开源 MQTT Broker,支持MQTTv3.1 和 MQTTv3.1.1 协议,也是目前主流的开源 MQTT Broker,它的官方地址是 https://mosquitto.org/。
使用 Mosquitto 开源项目来搭建一个属于自己的 MQTT 服务器其实非常简单,如果有兴趣的读者可以百度搜索“搭建 Mosquitto服务器”便可找到一大堆相关的文章。
2、公用 MQTT 服务器
我们还可以使用现有的 MQTT 服务器,譬如阿里云、百度云、华为云等提供的 MQTT 服务,不过这些大平台貌似都是收费的;对于我们学习测试来说非常不友好,那既然如此我们还有别的选择吗?当然有,我们可以使用公用 MQTT 服务器,这些服务器都是免费供大家学习测试使用的,需要注意的是这些公用 MQTT 服务器仅用于学习测试,不可拿来商用!
然也物联(国内)
官网地址:http://www.ranye-iot.net
MQTT 服务器地址:test.ranye-iot.net
TCP 端口:1883
TCP/TLS 端口:8883
通信猫(国内)
MQTT 服务器地址:mq.tongxinmao.com
TCP 端口:1883
推荐大家使用国内的这些 MQTT 服务器,连接快、延迟低!
后续笔者将使用然也物联的 MQTT 服务器进行测试,当然也推荐大家使用这个,与本教程保持一致!
3、使用手机作为客户端
手机也可以作为 MQTT 客户端,同样也需要安装一个客户端软件,这里笔者使用的是 MQTTool 工具,在手机的应用商城可以找到该软件,下载安装即可!
九、MQTT 协议(下)
1、QoS 是什么?
QoS 是 Quality of Service 的缩写,所以中文名便是服务质量。一个物联网通信中有些信息非常重要,我们需要确保这类重要信息可以准确无误的发送和接收,而有些信息则相对不那么重要,这类信息如果在传
输中丢失不会影响系统的运行;QoS 便用于告诉客户端或服务器哪些信息是重要信息,需要准确无误的传输、不可丢失;哪些信息不是那么重要,即使在传输过程中丢失也无妨!
MQTT 设计了一套保证消息稳定传输的机制,包括消息应答、存储和重传。在这套机制下,提供了三种不同级别的 QoS(Quality of Service),也就是 MQTT 协议有三种服务质量等级:
QoS = 0:最多发一次:最多一次,不管发送是否失败!发送端一旦发送完消息后,就完成任务了,发送端不会检查发出的消息能否被正确接收到。
QoS = 1:最少发一次
发送端向接收端发送 PUBLISH 报文,当接收端收到 PUBLISH 报文后会向发送端回复一个 PUBACK 报文,如果发送端收到 PUBACK 报文,那么它就知道消息已经被接收端成功接收!
假如过了一段时间后,发送端没有收到 PUBACK 报文,那么发送端会再次发送消息(发送 PUBLISH报文),然后再次等待接收端的 PUBACK 确认报文。因此,当 QoS=1 时,发送端在一定时间内没有收到接收端的 PUBACK 确认报文,会重复发送同一条消息。
注意:Qos=1 时,MQTT 服务器是不会进行去重的,只要发布者或者服务器没有收到 PUBACK 报文,就认为主题消息没有发送成功进入重发;服务器或者订阅者,不会根据 dup 标志的值进行去重(也就是说
协议本身不会去重),需要我们的客户端应用程序去进行判断、处理。
QoS = 2:保证收一次
当 MQTT 服务质量为 2 级时,MQTT 协议可以确保接收端只接收一次消息(注意是只接收到一次,在 QoS=1 的情况下,接收端接收到消息的次数可能不止一次:>=1)。
为了确保接收端只接收到一次消息,PUBLISH 报文的收发过程相对更加复杂。发送端需要接收端进行两次消息确认,因此,2 级 MQTT 服务质量是最安全的服务级别,也是最慢的服务级别。我们来看看整体
的过程:
从上到下,按照 1、2、3、4 的顺序进行:
①、首先发送端向接收端发送 PUBLISH 报文;
②、接收端接收到 PUBLISH 报文后,向发送端回复一个 PUBREC 报文(官方称其为--发布收到);
③、发送端接收到 PUBREC 报文后,会再次向接收端发送 PUBREL 报文(官方称其为--发布释放);
④、接收端接收到 PUBREL 报文后,会再次向发送端回复一个 PUBCOMP 报文(官方称其为--发布完成),如果发送端接收到 PUBCOMP 报文表示消息传输成功,它确认接收端已经成功接收到消息,整个过
程结束!
注意:要想实现 QoS>0 的 MQTT 通信,客户端在连接服务端时务必要将
cleanSession 设置为 false。如果这一步没有实现,那么客户端是无法实现 QoS>0 的 MQTT 通信,因为如果 cleanSession 设置为 true,则
意味着客户端不会接收到任何离线消息,包括 QoS1 和 QoS2 的情况。所以这一点非常关键,请您务必要留意。
十、保留消息
PUBLISH 报文中有一个 retain保留标志,是一个布尔值,当 retain 设置为true 时表示保留消息,如果设置为 false 表示不保留消息。
应用场景: 比如客户端每小时整点发布消息,比如7:00发布了一条,但由于某种原因订阅这条信息的显示客户端重启了,重启后7:05分,但是下一次发布要到8:00,这时候问题就出现了。
为避免以上情况,我们可以让室温测量客户端在每次向室温主题发布消息时将 retain 标志设置为true,以告诉服务端接收到此消息之后需要保留这个消息,这样服务端就会将该消息进行存储、保留,无论显示客户端在任何时间订阅室温主题,订阅之后都会马上收到该主题中的“保留消息”,也就是温度测量客户端发布的最新室温消息。
注意:每一个主题只能有一个“保留消息”。重新发布主题是新的“保留消息”会覆盖旧的“保留消息”,如果我们要删除保留消息,只需要向该主题发布一条空的“保留消息”即可。
点击 Publish 按钮发布消息,之后我们再重新订阅主题“dt2914/testTopic”,如下:
当订阅之后我们的客户端立马就收到了一条消息,并且还标识了这条消息是“保留消息”。这就是“保留消息”的作用。
十、MQTT 的心跳机制
原理:让客户端在没有向服务端发送消息的这个空闲时间里,定时向服务端发送一个心跳包,这个心跳包被称为心跳请求,其实质就是向服务端发送一个 PINGREQ 报文;当服务端收到PINGREQ 报文后就知道该客户端依然在线,然后向客户端回复一个 PINGRESP 报文,称为心跳响应!(通过 keepAlive 设置时间间隔)如下图所示:
十一、MQTT 的遗嘱机制
客户端断开与服务端的连接通常是有两种方式的:
⚫ 客户端主动向服务端发送 DISCONNECT 报文,请求断开连接,自然服务端也就知道了客户端要离线了;
⚫ 客户端意外掉线。被动与服务端断开了连接。
MQTT 协议允许客户端在“活着”的时候就写好遗嘱,这样一旦客户端意外断线,服务端就可以将客户端的遗嘱公之于众。请注意,在这段话中,我将意外断线这几个字特意做了加粗处理,这是因为,客户端的遗嘱只在意外断线时才会发布,如果客户端正常的断开了与服务端的连接(主动断开),这个遗嘱机制是不会启动的,服务端也不会将客户端的遗嘱公布。
那什么是意外断线?其实除了客户端主动向服务端发送 DISCONNECT 报文请求断开连接这种情况之外,其它断线的情况都属于意外断开连接,譬如网络不稳定、客户端设备没电关机了等。
客户端如何设置自己的“遗嘱”信息
这几个参数都是以 will 开头的,will 其实就是“遗嘱”的英文单词,所以由此可知,遗嘱需要在客户端连接服务端时就需要设置好,下面分别介绍一下:
willTopic -- 遗嘱主题
遗嘱消息和普通 MQTT 消息很相似,也有主题和正文内容。willTopic 的作用正是告知服务端,本客户端的遗嘱主题是什么。只有那些订阅了这一遗嘱主题的客户端才会收到本客户端的遗嘱消息。
以上图为例,此遗嘱主题为“clientWill”,也就是说,只有订阅了主题“clientWill”的客户端,才会收到这台客户端的遗嘱消息。当然,客户端也可以主动向遗嘱主题发布消息,这样通常会有一些妙用,譬如当客户端 A 上线时可以向自己的遗嘱主题发布一条消息,那么那些订阅了该遗嘱主题的客户端可以收到这条消息,这些订阅者也就知道了客户端 A 已经上线了。所以这个可以用来实现一个上线通知的小功能。
willMessage -- 遗嘱消息
遗嘱消息定义了遗嘱的内容。在本示例中,那些订阅了主题“clientWill”的客户端会在客户端意外断线时,收到服务端发布的“client offline”这样的信息。
willRetain -- 遗嘱消息的保留标志
遗嘱消息也可以设置为保留标志,用于告诉服务端是否需要对遗嘱消息进行保留处理。
willQoS -- 遗嘱消息的 QoS
对于遗嘱消息来说,同样可以使用服务质量来控制遗嘱消息的传递和接收。这里的服务质量与普通MQTT 消息的服务质量是一样的概念。也可以设置为 0、1、2。对于不同的服务质量级别,服务端会使用不同的服务质量来发布遗嘱消息。
MQTT.fx 如何设置客户端的“遗嘱”
十一、MQTT 用户密码认证
有些 MQTT 服务端需要客户端在连接时提供用户名和密码,只有客户端正确提供了用户名和密码后,才能连接服务端,否则服务端将会拒绝客户端连接,那么客户端也就无法发布和订阅消息了。但有些 MQTT服务端并不需要客户端提供用户名、密码进行认证,譬如我们前面使用的公用 MQTT 服务器,无需提供用户名和密码也可连接。所以这个 username 和 password 是可选的参数,而非必须的,重点在于 MQTT 服务端是否需要用户名、密码认证。
用户名和密码除了用于在连接服务端时进行认证、校验这一功能外,有些 MQTT 服务端也利用此信息来识别客户端属于哪一个用户,从而对客户端进行管理。譬如用户可以拥有私人主题,这些主题只有该用户可以发布和订阅;对于私人主题,服务端就可以利用客户端连接时的用户名和密码来判断该客户端是否有发布订阅该用户私人主题的权限。
申请社区版 MQTT 服务
我们都是使用的公共版 MQTT 服务器进行了测试,既然是公共版,那就意味着大家都可以使用,只要输入了正确的服务器地址就可以连接,大家都可以对相同的主题发布消息、订阅该主题,导致我们发布的信息谁都能看到(只要它订阅了这个主题),这对物联网项目的安全性以及实用性方面来说都是不合适的,所以这些公用 MQTT 服务也仅供用于测试、学习!
事实上,然也物联平台也提供了免费的社区版 MQTT 服务,社区版 MQTT 服务是面向个人用户的免费MQTT 服务。与公共版 MQTT 服务不同的是,社区版 MQTT 服务中,用户个人主题和信息传输受到用户名和密码保护。即,A 用户的个人主题只有 A 用户可以发布和订阅,其他用户不能对该主题进行订阅和发布,这样会使得安全性得到提升。
当客户端连接社区版 MQTT 服务端时,需要提供正确的用户名和密码,服务端会对此进行验证,如果没有提供正确的用户名、密码信息,则服务器将拒绝为用户提供 MQTT 服务,也就是拒绝客户端连接。所以,我们个人用户可使用然也物联的社区版 MQTT 服务来搭建自己的私人物联网项目,注意仅限于个人用户使用,不可商用!
那接下来,笔者将向大家介绍如何通过然也物联平台申请社区版 MQTT 服务。首先进入到然也物联的官方网站:http://www.ranye-iot.net/
大家根据指示填写信息,完成用户注册。注册完成之后登陆然也物联平台,登陆成功之后如下所示:
左上角会出现一个“平台申请”链接,点击“平台申请”链接即可申请然也物联的社区版 MQTT 服务,如下所示:
同样,大家根据指示说明填写好信息。在填写信息之前,仔细阅读相关说明以及相应的要求,最后有三个题目需要大家填写正确,只有正确回答所有问题最终才会显示“提交申请”这个按钮。这样做为了防止申请服务的用户是真正需要使用 MQTT 服务的用户、而不是随随便便的一个用户。
当我们提交申请之后,页面会出现一个提示信息,这个大家要认真看一下,提示中说到:然也物联官方工作人员会在未来几天之内添加你申请时留下的微信号,以人工的形式进一步完成申请审核。所以大家要留意下自己的微信,未来几天内会不会有然也物联官方工作人员添加你为好友,到时你要同意一下。
工作人员会通过微信通知我们,告诉我们申请已经成功了!接着工作人员会将相关的使用注意事项、使用方法通过微信发送给您,大家要认真阅读尤其是注意事项;如果违反了它的规定,将会停止您使用社区版服务。除了注意事项之外,还包括对于我们使用社区版 MQTT 服务非常重要的信息,譬如社区版 MQTT 服务器的地址(iot.ranye-iot.net)、端口(1883)以及然也物联给我们提供的客户端连接认证信息。
然也物联给每一个申请的用户提供了 8 组客户端连接认证信息,也就是 8 组用户名、密码、clientId,也就是说允许我们同时使用 8 台客户端设备连接服务端;每一台客户端设备连接服务端时使用其中一组用户名、密码、clientId 信息,只有用户名、密码、clientId 匹配、服务端的认证才会通过、才可成功连接到服务端。
社区版 MQTT 服务器为用户提供了个人专属主题级别,只有用户自己的客户端设备才可以使用自己的个人专属主题级别,譬如向个人专属主题发布消息、订阅个人专属主题;而其它用户是无法向您的个人专属主题发布消息、也不能订阅您的个人专属主题,因为社区版 MQTT 服务中个人专属主题级别受到了用户名、密码保护,同样您也不能向其他用户的个人专属主题发布消息以及订阅其他用户的个人专属主题;这样使得我们的 MQTT 物联网通信安全性大大提升!
十二、移植 MQTT 客户端库
首先我们进入到 MQTT 的官网地址:https://mqtt.org/ 下载 MQTT 客户端库源码包。
MQTT 客户端库支持多种不同的编程语言,我们使用的是 C 语言开发,所以要选择 MQTT C 客户端库,如下所示:
这里有多种不同的 MQTT C 客户端库,笔者推荐大家使用第一个 Eclipse Paho C,这是一个“MQTT CClient for Posix and Windows”,Paho MQTT C 客户端库是用 ANSI 标准 C 编写的功能齐全的 MQTT 客户端库,可运行在 Linux 系统下,支持 MQTT3.1、MQTT3.1.1、MQTT5.0。
点击“Eclipse Paho C”链接地址,如下:
在这个页面中会有一些简单地介绍信息,大家可以自己看一看。我们往下看,找到它的下载地址:
点击右边的“Release”找到它的发布版本,如下所示:
目前最新的版本是 1.3.9,我们不使用最新版本,建议大家使用 1.3.8 版本,如上图所示,点击“Sourcecode (tar.gz)”链接地址下载客户端库源码。
下载成功之后会得到如下压缩文件:
交叉编译 MQTT C 客户端库源码
将 paho.mqtt.c-1.3.8.tar.gz 压缩文件拷贝到 Ubuntu 系统某个目录下,如下所示:
接着将其解压到当前目录,如下所示:
解压成功之后会得到 paho.mqtt.c-1.3.8 文件夹,这就是 paho MQTT C 客户端库源码工程,进入到该目录下,可以看到工程顶级目录下有一个 CMakeLists.txt 文件,所以可知这是一个由 cmake 构建的工程。
首先我们要新建一个交叉编译配置文件 arm-linux-setup.cmake,进入到 cmake 目录下,新建 arm-linuxsetup.cmake 文件,并输入以下内容:
##################################
# 配置 ARM 交叉编译
#################################
set(CMAKE_SYSTEM_NAME Linux) #设置目标系统名字
set(CMAKE_SYSTEM_PROCESSOR arm) #设置目标处理器架构
# 指定编译器的 sysroot 路径
set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots)
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)
# 指定交叉编译器 arm-linux-gcc
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm
poky-linux-gnueabi-gcc)
# 为编译器添加编译选项
set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
#################################
# end
##################################
这是配置交叉编译,需要根据自己实际情况修改。编写完成之后保存退出。
回到工程的顶层目录,新建一个名为 build 的目录,如下所示:
进入到 build 目录下,执行 cmake 进行构建:
~/tools/cmake-3.16.0-Linux-x86_64/bin/cmake 这是第三十二章时笔者下载的 3.16.0 版本的 cmake 工具,您 得 根 据 自 己 的 实 际 路 径 来 指 定 ; CMAKE_BUILD_TYPE 、 CMAKE_INSTALL_PREFIX 、
CMAKE_TOOLCHAIN_FILE 都是 cmake 变量,CMAKE_INSTALL_PREFIX 这个指定了安装路径,笔者将安装路径设置为顶层目录下的 install 目录。
除此之外,还定义了两个缓存变量 PAHO_WITH_SSL 和 PAHO_BUILD_SAMPLES,具体是什么意思大家可以自己查看工程顶级目录下的 README.md 文件,在 README.md 文件中对工程的编译进行了简单介绍。
cmake 执行完毕之后,接着执行 make 编译:
这里需要给大家简单地说明一下,事实上,MQTT 客户端库依赖于 openssl 库,所以通常在移植 MQTT客户端库的时候,需要先移植 openssl、交叉编译 openssl 得到库文件以及头文件,然后再来编译 MQTT 客
户端库;但我们这里没有去移植 openssl,原因是,我们的开发板出厂系统中已经移植好了 openssl 库,并且我们所使用的交叉编译器在编译工程源码的时候会链接 openssl 库(sysroot 路径指定的)。
编译成功之后,执行 make install 进行安装:
对安装目录下的文件夹进行简单介绍
进入到安装目录下:
在安装目录下有 bin、include、lib 以及 share 这 4 个文件夹,bin 目录下包含了一些简单的测试 demo,lib 目录下包含了我们编译出来的库文件,如下所示:
一共有 4 种类型的库,这里我们简单地介绍一下:
⚫ libpaho-mqtt3a.so:异步模式 MQTT 客户端库(不支持 SSL)。
⚫ libpaho-mqtt3as.so:异步模式 MQTT 客户端库(支持 SSL)。
⚫ libpaho-mqtt3c.so:同步模式 MQTT 客户端库(不支持 SSL)。
⚫ libpaho-mqtt3cs.so:支持 SSL 的同步模式客户端库(支持 SSL)。
MQTT 中使用 SSL/TLS 来提供安全性(由 openssl 提供),使用 SSL 来做一些加密验证,使得数据传输更加安全可靠。
以上便给大家简单地介绍了下这 4 种库文件之间的区别,那后续我们将使用 libpaho-mqtt3c.so。
介绍完库文件之后,再来看看头文件,进入到 include 目录下:
在我们的 MQTT 客户端应用程序中只需要包含 MQTTAsync.h 或 MQTTClient.h 头文件即可,其它那些头文件会被这两个头文件所包含;MQTTAsync.h 是异步模式客户端库对外的头文件,而 MQTTClient.h 则是
同步模式客户端库对外的头文件。因为后续我们将使用同步模式,所以到时在我们的应用程序中需要包含MQTTClient.h 头文件。
拷贝库文件到开发板
将编译得到的库文件拷贝到开发板 Linux 系统/usr/lib 目录下,注意不要破坏原有的链接关系,建议在操作之前,先将库文件进行打包,如下所示:
将压缩包文件 libmqtt.tar.gz 拷贝到开发板 Linux 系统/home/root 目录下,然后将其解压到/usr/lib 目录:tar -xzf libmqtt.tar.gz -C /usr/lib
十三、MQTT 客户端库 API 介绍
上小节我们已经移植了 MQTT 客户端库到我们的开发板,接下来我们便可以开始编写 MQTT 客户端应用程序了,在编写应用程序之前,笔者需要向大家简单地介绍一下 MQTT 客户端库提供的 API。
本小节我们所介绍的这些库函数都是定义在 MQTTClient.h 头文件中,也就是同步模式客户端库 API,所以在我们的应用程序中需要包含头文件 MQTTClient.h。
Tips:MQTT 客户端源码顶级目录 docs/MQTTClient/html/index.html 文档向用户介绍了 API 的使用方法,并且提供了相应示例代码供用户参考。
1、MQTTClient_message 结构体
这里向大家介绍一个结构体 MQTTClient_message,该结构体很重要,MQTT 客户端应用程序发布消息和接收消息都是围绕着这个结构体。MQTTClient_message 数据结构描述了 MQTT 消息的负载和属性等相关
信息,譬如消息的负载、负载的长度、qos、消息的保留标志、dup 标志等,但是消息主题不是这个结构体的一部分。该结构体内容如下:
当客户端发布消息时就需要实例化一个 MQTTClient_message 对象,同理,当客户端接收到消息时,其实也就是接收到了 MQTTClient_message 对象。通常在实例化 MQTTClient_message 对象时会使用
MQTTClient_message_initializer 宏对其进行初始化。
2、创建一个客户端对象
在连接服务端之前,需要创建一个客户端对象,使用 MQTTClient_create 函数创建:
handle:MQTT 客户端句柄;
serverURL:MQTT 服务器地址;
clientId:客户端 ID;
persistence_type:客户端使用的持久化类型:
⚫ MQTTCLIENT_PERSISTENCE_NONE:使用内存持久性。如果运行客户端的设备或系统出现故障或关闭,则任何传输中消息的当前状态都会丢失,并且即使在 QoS1 和 QoS2 下也可能无法传递某
些消息。
⚫ MQTTCLIENT_PERSISTENCE_DEFAULT:使用默认的(基于文件系统)持久性机制。传输中消息的状态保存在文件系统中,并在意外故障的情况下提供一些防止消息丢失的保护。
⚫ MQTTCLIENT_PERSISTENCE_USER:使用特定于应用程序的持久性实现。使用这种类型的持久性可以控制应用程序的持久性机制。应用程序必须实现 MQTTClient_persistence 接口。
persistence_context:如果使用 MQTTCLIENT_PERSISTENCE_NONE 持久化类型,则该参数应设置为NULL。如果选择的是 MQTTCLIENT_PERSISTENCE_DEFAULT 持久化类型,则该参数应设置为持久化目
录的位置,如果设置为 NULL,则持久化目录就是客户端应用程序的工作目录。
返回值:客户端对象创建成功返回 MQTTCLIENT_SUCCESS,失败将返回一个错误码。
使用示例:
注意,"tcp://iot.ranye-iot.net:1883"地址中,第一个冒号前面的 tcp 表示我们使用的是 TCP 连接;后面的1883 表示 MQTT 服务器对应的端口号。
3、连接服务端
客户端创建之后,便可以连接服务器了,调用 MQTTClient_connect 函数连接:
handle:客户端句柄;
options:一个指针。指向一个 MQTTClient_connectOptions 结构体对象。MQTTClient_connectOptions 结构体中包含了 keepAlive、cleanSession 以及一个指向 MQTTClient_willOptions 结构体对象的指针 will_opts;
MQTTClient_willOptions 结构体包含了客户端遗嘱相关的信息,遗嘱主题、遗嘱内容、遗嘱消息的 QoS 等级、遗嘱消息的保留标志等。
返回值:连接成功返回 MQTTCLIENT_SUCCESS,是否返回错误码:
⚫ 1:连接被拒绝。不可接受的协议版本,不支持客户端的 MQTT 协议版本
⚫ 2:连接被拒绝:标识符被拒绝
⚫ 3:连接被拒绝:服务器不可用
⚫ 4:连接被拒绝:用户名或密码错误
⚫ 5:连接被拒绝:未授权
⚫ 6-255:保留以备将来使用
使用示例:
通常在定义 MQTTClient_connectOptions 对象时会使用 MQTTClient_connectOptions_initializer 宏对其进行初始化操作;而在定义 MQTTClient_willOptions 对象时使用 MQTTClient_willOptions_initializer 宏对其初
始化。
4、设置回调函数
调用 MQTTClient_setCallbacks 函数为应用程序设置回调函数,MQTTClient_setCallbacks 可设置多个回调函数,包括:断开连接时的回调函数 cl(当客户端检测到自己掉线时会执行该函数,如果将其设置为 NULL表示应用程序不处理断线的情况)、接收消息的回调函数 ma(当客户端接收到服务端发送过来的消息时执行该函数,必须设置此函数否则客户端无法接收消息)、发布消息的回调函数 dc(当客户端发布的消息已经确认发送时执行该回调函数,如果你的应用程序采用同步方式发布消息或者您不想检查是否成功发送时,您可以将此设置为 NULL)。
handle:客户端句柄;
context:执行回调函数的时候,会将 context 参数传递给回调函数,因为每一个回调函数都设置了一个参数用来接收 context 参数。
cl:一个 MQTTClient_connectionLost 类型的函数指针,如下:
typedef void MQTTClient_connectionLost(void *context, char *cause);参数 cause 表示断线的原因,是一个字符串。
ma:一个 MQTTClient_messageArrived 类型的函数指针,如下:
参 数 topicName 表 示消息的主题名, topicLen 表示主题名的长 度;参数 message 指向一 个MQTTClient_message 对象,也就是客户端所接收到的消息。
dc:一个 MQTTClient_deliveryComplete 类型的函数指针,如下:
参 数 dt 表 示 MQTT 消 息 的 值 , 将 其 称 为 传 递 令 牌 。 发 布 消 息 时 ( 应 用 程 序 通 过MQTTClient_publishMessage 函数发布消息),MQTT 协议会返回给客户端应用程序一个传递令牌;应用程序可以通过将调用 MQTTClient_publishMessage()返回的传递令牌与传递给此回调的令牌进行匹配来检查消息是否已成功发布。
前面提到了“同步发布消息”这个概念,既然有同步发布,那必然有异步发布,确实如何!那如何控制是同步发布还是异步发布呢?就是通过 MQTTClient_connectOptions 对象中的 reliable 成员控制的,这是一个布尔值,当 reliable=1 时使用同步方式发布消息,意味着必须完成当前正在发布的消息(收到确认)之后才能发布另一个消息;如果 reliable=0 则使用异步方式发布消息。
当使用 MQTTClient_connectOptions_initializer 宏对 MQTTClient_connectOptions 对象进行初始化时,reliable 标志被初始化为 1,所以默认是使用了同步方式。
返回值:成功返回 MQTTCLIENT_SUCCESS,失败返回 MQTTCLIENT_FAILURE。
注意:调用 MQTTClient_setCallbacks 函数设置回调必须在连接服务器之前完成!
使用示例:
对于 msgarrvd 函数有两个点需要注意:
⚫ 退出函数之前需要释放消息的内存空间,必须调用 MQTTClient_freeMessage 函数;同时也要释放主题名称占用的内存空间,必须调用 MQTTClient_free。
⚫ 函数的返回值。此函数的返回值必须是 0 或 1,返回 1 表示消息已经成功处理;返回 0 则表示消息处理存在问题,在这种情况下,客户端库将重新调用 MQTTClient_messageArrived()以尝试再次将
消息传递给客户端应用程序,所以返回 0 时不要释放消息和主题所占用的内存空间,否则重新投递失败。
当 客 户 端 成 功 连 接 到 服 务 端 之 后 , 便 可 以 发 布 消 息 或 订 阅 主 题 了 , 应 用 程 序 通 过MQTTClient_publishMessage 库函数来发布一个消息:
handle:客户端句柄;
topicName:主题名称。向该主题发布消息。
msg:指向一个 MQTTClient_message 对象的指针。
dt:返回给应用程序的传递令牌。
返回值:成功返回 MQTTCLIENT_SUCCESS,失败返回错误码。
使用示例:
6、订阅主题和取消订阅主题
客户端应用程序调用 MQTTClient_subscribe 函数来订阅主题:
handle:客户端句柄;
topic:主题名称。客户端订阅的主题。
qos:QoS 等级。
返回值:成功返回 MQTTCLIENT_SUCCESS,失败返回错误码。
使用示例:
当客户端想取消之前订阅的主题时,可调用 MQTTClient_unsubscribe 函数,如下所示:
handle:客户端句柄;
topic:主题名称。取消订阅该主题。
返回值:成功返回 MQTTCLIENT_SUCCESS,失败返回错误码。
7、断开服务端连接
当客户端需要主动断开与客户端连接时,可调用 MQTTClient_disconnect 函数:
handle:客户端句柄;
timeout:超时时间。客户端将断开连接延迟最多 timeout 时间(以毫秒为单位),以便完成正在进行中的消息传输。
返回值:如果客户端成功从服务器断开连接,则返回 MQTTCLIENT_SUCCESS;如果客户端无法与服务器断开连接,则返回错误代码。