物联网曾被认为是继计算机、互联网之后,信息技术行业的第三次浪潮。随着基础通讯设施的不断完 善,尤其是 5G 的出现,进一步降低了万物互联的门槛和成本。物联网本身也是 AI 和区块链应用很好的落 地场景之一,各大云服务商也在纷纷上架物联网平台和服务。
物联网通讯是物联网的一个核心内容,目前物联网的通讯协议并没有一个统一的标准,比较常见的有 MQTT、CoAP、DDS、XMPP 等,在这其中,MQTT (消息队列遥测传输协议) 应该是应用最广泛的标准之 一。 目前, MQTT 已逐渐成为 IoT 领域最热门的协议, 也是国内外各大物联网平台最主流的传输协议,阿里 云 IoT 物联网平台很多设备都是通过 MQTT 接入。
本章我们将从最基础的知识开始,向您讲解 MQTT 协议的应用,通过本章的学习, 我们将一起在开发 板上实现一个简单地物联网小项目, 实现远程控制开发板上的外设,譬如 LED、蜂鸣器;亦或者远程获取 开发板运行的状态信息。
本章将会讨论如下主题内容。
⚫ MQTT 是什么?
⚫ MQTT 协议介绍
⚫ MQTT 客户端库移植到开发板
⚫ 如何开发 MQTT 客户端应用程序
MQTT 简介
《MQTT 协议规范中文版》一书中对 MQTT (Message Queuing Telemetry Transport ,消息队列遥测传 输) 进行了描述:
MQTT 是一种基于客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、 简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与 机器的通信(M2M)以及物联网环境(IoT)。 ----MQTT 协议中文版
以上这段话很好的描述了 MQTT 的全部含义,它是一种轻巧、开放、简单、规范的网络通信协议。与 HTTP 协议一样,MQTT 协议也是应用层协议,工作在 TCP/IP 四层模型中的最上层(应用层),构建于 TCP/IP 协议上。 MQTT 最大优点在于,可以以极少的代码和有限的带宽, 为连接远程设备提供实时可靠的消息服 务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的 应用。
如今,MQTT 成为了最受欢迎的物联网协议,已广泛应用于车联网、智能家居、即时聊天应用和工业互 联网等领域。目前通过 MQTT 协议连接的设备已经过亿,这些都得益于 MQTT 协议为设备提供了稳定、可 靠、易用的通信基础。
MQTT 的主要特性
MQTT 协议是为工作在低带宽、不可靠网络的远程传感器和控制设备之间的通讯而设计的协议,它具 有以下主要的几项特性:
①、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
②、基于 TCP/IP 提供网络连接。
主流的 MQTT 是基于 TCP 连接进行数据推送的,但是同样也有基于 UDP 的版本, 叫做 MQTT-SN。这 两种版本由于基于不同的连接方式, 优缺点自然也就各有不同了。
③、支持 QoS 服务质量等级。
根据消息的重要性不同设置不同的服务质量等级。
④、小型传输,开销很小,协议交换最小化, 以降低网络流量。
这就是为什么在介绍里说它非常适合"在物联网领域,传感器与服务器的通信,信息的收集",要知道嵌 入式设备的运算能力和带宽都相对薄弱,使用这种协议来传递消息再适合不过了,在手机移动应用方面, MQTT 是一种不错的 Android 消息推送方案。
⑤、使用 will 遗嘱机制来通知客户端异常断线。
⑥、基于主题发布/订阅消息,对负载内容屏蔽的消息传输。
⑦、支持心跳机制。
以上便是 MQTT 的主要特性了。
MQTT 最初是用于石油管道的传感器与卫星之间数据传输。他们当时正在开发一个利用卫星通讯监控 输油管道的项目,为了实现这个项目要求,他们需要开发一种用于嵌入式设备的通讯协议,这种通讯协议必 须满足以下条件:
⚫ 易于实现, 服务器必须要实现成千上万个客户端的接入
⚫ 数据传输的服务质量可控,根据数据的重要性和特性,设置不同等级的服务质量
⚫ 占用带宽小,单次数据量小,但不能出错
⚫ 必须能够适应高延迟、掉线、断网等网络通信不可靠的风险
⚫ 设备连接状态可知,云端与设备端保持长连接 通过以上几个条件可知:
⚫ MQTT 服务器可以连接大量的远程传感器和控制设备,与远程客户端保持长连接,具有一定的实 时性。
⚫ 云端向设备端发送消息, 设备端可以在最短的时间内接收到并作出回应。
⚫ MQTT 更适合需要实时控制的场合, 尤其适合执行器。
⚫ 云端与客户端需要保持长连接,要能够获取到设备的连接状态,就需要时不时地发送心跳包,这就 不会省电, 所以,MQTT 并不适合低功耗场合。
从以上几点不难看出, MQTT 从诞生之初就是专为低带宽、高延迟或不可靠的网络而设计的。虽然历 经几十年的更新和变化,以上这些特点仍然是 MQTT 协议的核心特点。但是与最初不同的是,MQTT 协议 已经从嵌入式系统应用拓展到开放的物联网(IoT) 领域。
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 3.1.1 与 MQTT 5
MQTT 协议 (上)
在上小节中,我们了解了 MQTT 的背景知识和基本特点,本小节开始我们将一起了解 MQTT 通信基本 原理。
MQTT 通信基本原理
MQTT 是一种基于客户端-服务端架构的消息传输协议, 所以在 MQTT 协议通信中, 有两个最为重要的 角色, 它们便是服务端和客户端。
服务端
MQTT 服务端通常是一台服务器 (broker), 它是 MQTT 信息传输的枢纽, 负责将 MQTT 客户端发送 来的信息传递给 MQTT 客户端;MQTT 服务端还负责管理 MQTT 客户端,以确保客户端之间的通讯顺畅, 保证 MQTT 信息得以正确接收和准确投递。
客户端
MQTT 客户端可以向服务端发布信息,也可以从服务端收取信息;我们把客户端发送信息的行为称为 “发布”信息。而客户端要想从服务端收取信息,则首先要向服务端“订阅”信息。“订阅”信息这一操作 很像我们在使用微信时“关注”了某个公众号,当公众号的作者发布新的文章时,微信官方会向关注了该公 众号的所有用户发送信息,告诉他们有新文章更新了,以便用户查看。
MQTT 主题
上面我们讲到了,客户端想要从服务器获取信息,首先需要订阅信息,那客户端如何订阅信息呢?这里 我们要引入“主题(Topic) ”的概念, “主题”在 MQTT 通信中是一个非常重要的概念,客户端发布信息 以及订阅信息都是围绕“主题”来进行的, 并且 MQTT 服务端在管理 MQTT 信息时,也是使用“主题”来 控制的。
客户端发布消息时需要为消息指定一个“主题”,表示将消息发布到该主题;而对于订阅消息的客户端 来说,可通过订阅“主题”来订阅消息,这样当其它客户端或自己(当前客户端)向该主题发布消息时, MQTT 服务端就会将该主题的信息发送给该主题的订阅者(客户端)。
为了便于您更好理解服务端是如何通过“主题”来控制客户端之间的信息通讯,我们来看看下图实例:
在以上图示中一共有三个 MQTT 客户端,它们分别是开发板、手机和电脑。MQTT 服务端在管理MQTT 通信时使用了“主题”来对信息进行管理。比如上图所示,假设我们需要利用手机和电脑获取开发板在运行 过程中 SoC 芯片的温度,那么首先电脑和手机这两个客户端需要向 MQTT 服务器订阅主题“芯片温度”; 接下来,当开发板客户端向服务端的“芯片温度”主题发布信息 (假设信息的内容就是当前的温度值) 后,
服务端就会首先检查都有哪些客户端订阅了“芯片温度”这一主题的信息,而当它发现订阅了该主题的客户 端有一个手机和一个电脑,于是服务端就会将刚刚收到的“芯片温度”信息转发给订阅了该主题的手机和电 脑客户端。
通过以上的这种实例,手机和电脑便可以获取到开发板运行时 SoC 芯片的温度值。
以上实例中,开发板是“芯片温度”主题的发布者,而手机和电脑则是该主题的订阅者。
值得注意的是, MQTT 客户端在通信时, 角色往往不是单一的,一个客户端既可以作为信息发布者也 可以同时作为信息订阅者。如下图所示:
上图中的所有客户端都是围绕“LED 控制”这一主题进行通信。此时, 对于“LED 控制”这一主题来 说,手机和电脑客户端成为了 MQTT 信息的发布者而开发板则成为了MQTT 信息的订阅者(接收者) 。
所以由此可知, 针对不同的主题,MQTT 客户端可以切换自己的角色, 它们可能对主题 A 来说是信息 发布者,但是对于主题 B 就成了信息订阅者, 所以一个 MQTT 客户端它的角色并不是固定的,所以大家一 定要理解“主题”这个概念。
MQTT 发布/订阅特性
从以上实例我们可以看到,MQTT 通信的核心枢纽是 MQTT 服务端,它负责将 MQTT 客户端发送来的 信息传递给 MQTT 客户端,还负责管理 MQTT 客户端,以确保客户端之间的通讯顺畅, 保证 MQTT 信息 得以正确接收和准确投递。
正是因为有了服务端对 MQTT 信息的接收、储存、处理和发送,客户端在发布和订阅信息时,可以相 互独立、且在空间上可以分离、时间上可以异步,这就是 MQTT 发布/订阅的特性: 客户端相互独立、空间 上可分离、 时间上可异步,具体介绍如下:
⚫ 客户端相互独立: MQTT 客户端是一个个独立的个体,它们无需了解彼此的存在, 依然可以实现 信息交流。譬如在上面的实例中,开发板客户端在发布“芯片温度”信息时,开发板客户端本身完 全不知道有多少个 MQTT 客户端订阅了“芯片温度”这一主题;而订阅了“芯片温度”主题的手
机和电脑客户端也完全不知道彼此的存在,大家只要订阅了“芯片温度”这一主题, MQTT 服务端 就会在每次收到新信息时,将信息发送给订阅了“芯片温度”主题的客户端。
⚫ 空间上分离: 空间上分离相对容易理解,MQTT 客户端以及 MQTT 服务端它们在通信时是处于同 一个通信网络中的,这个网络可以是互联网或者局域网;只要客户端联网,无论他们远在天边还是 近在眼前, 都可以实现彼此间的通讯交流;其实网络通信本就是如此, 所以并不是 MQTT 通信所 特有的。
⚫ 时间上可异步: MQTT 客户端在发送和接收信息时无需同步。这一特点对物联网设备尤为重要, 前面我们也介绍了, MQTT 从诞生之初就是专为低带宽、高延迟或不可靠的网络而设计的,高延 迟和不可靠网络必然就会导致时间上的异步; 物联网设备在运行过程中发生意外掉线是非常正常 的情况,我们使用上面的实例二的场景来作说明, 当开发板在运行过程中,可能会由于突然断电 (假设开发板是通过电源适配器供电的) 导致掉线,这时开发板会断开与 MQTT 服务端的连接。 假设此时我们的手机客户端向开发板客户端所订阅的“LED 控制”主题发布了信息,而开发板恰 恰不在线,这时,MQTT 服务端可以将“LED 控制”主题的新信息保存,待开发板客户端再次上 线后, 服务端再将“LED 控制”信息推送给开发板。所以这就必然导致了, 手机发送信息与开发 板接收信息在时间上是异步的。
总结
本小节向大家介绍了 MQTT 通信的基本原理,在 MQTT 通信中, 1 个服务端、多个客户端之间围绕“主 题”进行了通信,所以本节重要在于大家需要理解各个客户端的相互关系以及服务端在其中所起的作用,并 且理解“主题”这个概念以及 MQTT 发布/订阅模式的特性,后面向大家介绍具体的通信过程时,要迅速的 反应过来。
讲到这里请您注意: 对于 MQTT 发布/订阅模式的特性, 我们总结的几个特点中都有一个“可”字。这 个“可”字意味着客户端彼此之间可以独立, 空间可以分离, 时间可以异步。在我们实际应用中, 客户端之 间的关系既可以独立也可以相互依存。在空间上,既可以相距甚远,也可以彼此相邻。在时间上,既可以异 步也可以同步。这个“可”字所体现的是 MQTT 通讯的灵活性。
连接 MQTT 服务端
MQTT 客户端之间想要实现通信,必须要通过 MQTT 服务端。所以,客户端无论是发布信息还是订阅 信息都必须先连接到服务端。下面我们来看一下, 客户端连接服务端的详细过程。
MQTT 客户端连接服务端总共包含了两个步骤:
①、首先客户端需要向服务端发送连接请求,这个连接请求实际上就是向服务端发送一个 CONNECT 报文, 也就是发送了一个 CONNECT 数据包。
②、MQTT 服务端收到连接请求后,会向客户端发送连接确认。 连接确认实际上是向客户端发送一个 CONNACK 报文,也就是 CONNACK 数据包。
以上就是 MQTT 客户端连接服务端的详细步骤,总结一句话就是: 客户端先向服务端发送 CONNECT 报文,服务端收到连接请求后,再向待连接的客户端发送 CONNACK 报文。接下来,我们来看看 CONNECT 报文和 CONNACK 报文数据包中包含了哪些信息。
CONNECT 报文
在上面的描述中我们看到,MQTT 客户端要想连接服务端,首先要向服务端发送 CONNECT 报文。如 果此 CONNECT 报文的格式或内容不符合 MQTT 规范,则服务器会拒绝客户端的连接请求。
CONNECT 报文包含的信息如下图所示:
所谓报文就是一个数据包,MQTT 报文组成分为三个部分:固定头(Fixed header) 、可变头(Variable header) 以及有效载荷(Payload,消息体) 。这里我们简单地介绍一下:
⚫ 固定头 (Fixed header) :存在于所有 MQTT 报文中,固定头中有报文类型标识, 可用于识别是哪 种 MQTT 报文, 譬如该报文是 CONNECT 报文还是 CONNACK 报文,亦或是其它类型报文。
⚫ 可变头 (Variable header) :存在于部分类型的 MQTT 报文中, 报文的类型决定了可变头是否存 在及其具体的内容。
⚫ 消息体 (Payload) :存在于部分类型的 MQTT 报文中, payload 就是消息载体的意思。
关于 MQTT 报文格式更加详细的内容,如果大家有兴趣可以自己去了解下,笔者在这里就不再啰嗦了!
回到的 CONNECT 报文中,从图33.2.5中可知, CONNECT 报文包含了很多的信息, 左边的是信息的 名称(变量名) ,右边则是信息的具体内容(变量的值) ,右边这些具体内容只是笔者给出的一个示例, 不 是所有的 CONNECT 报文中的clientId 信息内容都是“client-id”,这里只是举个例子而已!
另外也请注意, 上图中有些信息名称旁边标注了“可选”字样, 而有些则没有。那些没有标注“可选” 字样的信息是必须包含在 CONNECT 报文中的。而对于标注了“可选”字样的信息,CONNECT 报文既可 以包含它们也可以没有它们,具体具体情况而定!
接下来笔者将向大家介绍 CONNECT 报文中这些信息表示什么意思,有什么作用? 不过,考虑到我们 刚刚接触 MQTT 协议, 目前应先从最基础的内容开始学起,所以本小节我们只介绍那些未标注“可选”字 样的信息。
clientId--客户端 id
clientId 是 MQTT 客户端的标识,也就是 MQTT 客户端的名字, MQTT 服务端可通过 clientId 来区分不 同的客户端,MQTT 服务端用该标识来识别客户端。因此 clientId 必须是独立的,如果两个 MQTT 客户端 使用相同 clientId 标识, 服务端会把它们当成同一个客户端来处理。通常 clientId 是由一串字符所构成的, 譬如, 在上面的示例中, clientId 是“client-id”。
keepAlive--心跳时间间隔
对于 MQTT 服务器来说,它要判断一台MQTT 客户端是否依然与它保持着连接状态,可以检查这台客 户端是不是经常发送消息给服务端,如果服务端经常收到客户端的消息,那么没问题,这个客户端肯定在 线。
但是有些客户端并不经常发送消息给服务端,对于这种客户端, MQTT 协议使用了类似心跳检测的方 法来判断客户端是否在线。前面在介绍 MQTT 时,曾提到过 MQTT 支持心跳机制,心跳机制其实就是用来 判断客户端是否与服务端保持着连接的一种方法,也就是说通过心跳机制来检测客户端是否在线。 客户端 在没有向服务端发送信息时 (空闲时) ,可以定时向服务端发送一个心跳数据包, 这个心跳包也被称作心跳 请求,心跳请求的作用正是用于告知服务端, 当前客户端依然在线,服务端在收到客户端的心跳请求后, 会 回复一条消息,这条回复消息被称作心跳响应。
关于 MQTT 的心跳机制后面还会向大家进行讲解,这里便不再多说了!CONNECT 报文中的keepAlive 其实是指定了心跳时间间隔, 也就是客户端向服务端发送心跳包的时间间隔。譬如 keepAlive=60,表示告诉 服务端,客户端将会每隔 60 秒左右向服务端发送心跳包。
cleanSession--清除会话
所谓“清除会话”这一翻译源自 MQTT 官方文档中文版。这是一个布尔值,cleanSession 标志可用于控 制客户端与服务端在连接和断开连接时的行为,我们举个例子来进行说明, QQ、微信这些聊天软件大家都 用过, 假设当前你的 QQ 账号没有登录或者说当前处于离线状态, 与服务器断开了连接;而在离线期间, 你 的 QQ 好友给你发了几条信息; 由于当前你的 QQ 处于离线状态, 自然是接收不到好友发送过来的信息, 但 是,当你的 QQ 恢复连接状态时,立马会接收到好友在离线期间所发给你的信息。
而 cleanSession 就与这个有关系, 它是一个布尔值, 如果连接服务端时 cleanSession=0 ,当 MQTT 客户 端由离线(与服务端断开连接) 再次上线时, 离线期间发给客户端的所有 QoS>0 的消息仍然可以接收到; 如果连接服务端时 cleanSession=1 ,当 MQTT 客户端由离线(与服务端断开连接) 再次上线时,离线期间发
给客户端的所有消息一律接收不到。注意,这里我们提到了 QoS,关于 QoS 的概念后续再向大家介绍,这 里暂时先不去理会, 先记住有这么个东西。
说白了,想接收离线消息,客户端连接服务端时就必须使用cleanSession=0;除了这个作用之外, 如果 cleanSession=0,则 MQTT 服务端会在客户端断开连接之后“记住”MQTT 客户端在线期间所订阅的所有“主 题”; 也就是说,服务端会保存、存储客户端所订阅的主题。
如果 cleanSession=1,客户端既无法接收到离线消息、服务端也不会记住该客户端所订阅的主题, 服务 端不会保存客户端的会话状态,每次连接都是一次新的会话;既然是新的会话,那就不会保存以前的会话状 态信息,一切从“新”开始、从而丢弃以前的会话状态信息(包括: 离线期间发送给客户端的消息以及客户 端订阅的主题等) ,这就是“清除会话”的含义。
所以这就是 cleanSession 的作用,总的来说:cleanSession 设置为 1,表示此次连接将创建一个新的临时 会话,在客户端断开后,这个会话会自动销毁。而 cleanSession 设置为 0,表示创建一个持久性会话,在客 户端断开连接时,会话仍然保持并保存离线消息, 直到会话超时注销。
关于 cleanSession 就给大家介绍这么多,已经解释得很清楚了, 相信大家已经明白了。
以上就是 CONNECT 报文的主要内容了, 关于 CONNECT 报文中的其它内容,我们会在后续内容中给 大家讲解。下面再看看 MQTT 服务端接收到客户端发来的连接请求后所回复的 CONNACK 报文详细内容。
CONNACK 报文
CONNACK 报文包含的信息如下图所示:
CONNACK 报文包括两个信息,一个是 returnCode(连接返回码),另一个是 sessionPresent。以下是这两 个信息的说明:
returnCode--连接返回码
当服务端收到了客户端的连接请求后,会向客户端发送 returnCode(连接返回码) ,用来说明连接情况。 如果客户端与服务端成功连接,则返回数字“0”。如果未能成功连接,返回码将会是一个非零的数字,具 体这个数字的含义, 请见下表:
返回码 |
说明 |
0 |
连接成功 |
1 |
连接被服务端拒绝, 原因是不支持客户端的 MQTT |