什么是mqtt?
与HTTP 协议一样, MQTT 协议也是应用层协议 ,工作在 TCP/IP 四层模型中的最上层(应用层) ,**构建于 TCP/IP协议上。 MQTT 最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。**作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用
MQTT 协议是为工作在低带宽、不可靠网络的远程传感器和控制设备之间的通讯而设计的协议,它具有以下主要的几项特性:
①、 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
②、基于 TCP/IP 提供网络连接。
主流的 MQTT 是基于 TCP 连接进行数据推送的,但是同样也有基于 UDP 的版本,叫做 MQTT-SN。这两种版本由于基于不同的连接方式,优缺点自然也就各有不同了。
③、支持 QoS 服务质量等级。
根据消息的重要性不同设置不同的服务质量等级。
④、 小型传输, 开销很小,协议交换最小化,以降低网络流量。
这就是为什么在介绍里说它非常适合"在物联网领域,传感器与服务器的通信,信息的收集" ,要知道嵌入式设备的运算能力和带宽都相对薄弱,使用这种协议来传递消息再适合不过了, 在手机移动应用方面, MQTT 是一种不错的 Android 消息推送方案。
⑤、使用 will 遗嘱机制来通知客户端异常断线。
⑥、基于主题发布/订阅消息,对负载内容屏蔽的消息传输。
⑦、支持心跳机制。
MQTT 通信基本原理
服务端和客户端
MQTT 是一种基于客户端-服务端架构的消息传输协议,所以在 MQTT 协议通信中,有两个最为重要的角色,它们便是服务端和客户端
MQTT 主题
客户端想要从服务器获取信息,首先需要订阅信息,"主题"在 MQTT 通信中是一个非常重要的概念,客户端发布信息以及订阅信息都是围绕"主题"来进行的。
客户端发布消息时需要为消息指定一个"主题" , 表示将消息发布到该主题 ;而对于**订阅消息的客户端来说,可通过订阅"主题" 来订阅消息,**这样当其它客户端或自己(当前客户端)向该主题发布消息时, MQTT 服务端就会将该主题的信息发送给该主题的订阅者(客户端)。
在以上图示中一共有三个 MQTT 客户端, 它们分别是开发板、 手机和电脑 。 MQTT 服务端在管理 MQTT通信时使用了**"主题"来对信息进行管理**。比如上图所示,假设我们需要利用手机和电脑获取开发板在运行过程中 SoC 芯片的温度,那么首先电脑和手机这两个客户端需要向 MQTT 服务器订阅主题"芯片温度" ;接下来,当开发板客户端向服务端的"芯片温度"主题发布信息(假设信息的内容就是当前的温度值) 后服务端就会首先检查都有哪些客户端订阅了"芯片温度"这一主题的信息,而当它发现订阅了该主题的客户端有一个手机和一个电脑,于是服务端就会将刚刚收到的"芯片温度"信息转发给订阅了该主题的手机和电脑客户端。
以上实例中, 开发板是"芯片温度"主题的发布者,而手机和电脑则是该主题的订阅者。
值得注意的是, MQTT 客户端在通信时,角色往往不是单一的, 一个客户端既可以作为信息发布者也可以同时作为信息订阅者。如下图所示:
图中的所有客户端都是围绕"LED 控制"这一主题进行通信。此时,对于"LED 控制"这一主题来说,手机和电脑客户端成为了 MQTT 信息的发布者而开发板则成为了 MQTT 信息的订阅者(接收者)。
MQTT 发布/订阅特性
客户端相互独立
MQTT 客户端是一个个独立的个体, 它们无需了解彼此的存在,依然可以实现信息交流。
空间上分离
MQTT 客户端以及 MQTT 服务端它们在通信时是处于同一个通信网络中的, 这个网络可以是互联网或者局域网; 只要客户端联网,无论他们远在天边还是近在眼前,都可以实现彼此间的通讯交流;其实网络通信本就是如此,所以并不是 MQTT 通信所特有的。
时间上可异步
MQTT 客户端在发送和接收信息时无需同步。这一特点对物联网设备尤为重要
连接 MQTT 服务端
MQTT 客户端连接服务端总共包含了两个步骤:
①、首先客户端需要向服务端发送连接请求 ,这个连接请求实际上就是向服务端发送一个 CONNECT报文,也就是发送了一个 CONNECT 数据包。
②、MQTT 服务端收到连接请求后,会向客户端发送连接确认 。 连接确认实际上是向客户端发送一个CONNACK 报文 ,也就是 CONNACK 数据包。
总结一句话就是:客户端先向服务端发送 CONNECT报文,服务端收到连接请求后,再向待连接的客户端发送 CONNACK 报文
CONNECT 报文
如果此 CONNECT 报文的格式或内容不符合 MQTT 规范,则服务器会拒绝客户端的连接请求。
CONNECT 报文包含的信息如下图所示:
所谓报文就是一个数据包, MQTT 报文组成分为三个部分:固定头(Fixed header)、可变头(Variable header)以及有效载荷(Payload,消息体)。这里我们简单地介绍一下:
固定头(Fixed header) : 存在于所有 MQTT 报文中, 固定头中有报文类型标识,可用于识别是哪种 MQTT 报文,譬如该报文是 CONNECT 报文还是 CONNACK 报文,亦或是其它类型报文。
可变头(Variable header) : 存在于部分类型的 MQTT 报文中,报文的类型决定了可变头是否存在及其具体的内容。
消息体(Payload): 存在于部分类型的 MQTT 报文中, payload 就是消息载体的意思。
clientId--客户端 id
clientId 是 MQTT 客户端的标识,也就是 MQTT 客户端的名字,MQTT 服务端可通过 clientId 来区分不同的客户端。
keepAlive--心跳时间间隔
有些客户端并不经常发送消息给服务端, 对于这种客户端, MQTT 协议使用了类似心跳检测的方法来判断客户端是否在线 。客户端在没有向服务端发送信息时(空闲时) ,可以定时向服务端发送一个心跳数据包,这个心跳包也被称作心跳请求, 心跳请求的作用正是用于告知服务端,当前客户端依然在线。
譬如 keepAlive=60,表示告诉服务端,客户端将会每隔 60 秒左右向服务端发送心跳包。
cleanSession--清除会话
这是一个布尔值, cleanSession 标志可用于控制客户端与服务端在连接和断开连接时的行为,我们举个例子来进行说明, QQ、微信这些聊天软件大家都用过,假设当前你的 QQ 账号没有登录或者说当前处于离线状态,与服务器断开了连接; 而在离线期间,你的 QQ 好友给你发了几条信息; 由于当前你的 QQ 处于离线状态,自然是接收不到好友发送过来的信息,但是, 当你的 QQ 恢复连接状态时,立马会接收到好友在离线期间所发给你的信息
如果连接服务端时 cleanSession=0, 当 MQTT 客户端由离线(与服务端断开连接)再次上线时,离线期间发给客户端的所有 QoS>0 的消息仍然可以接收到;如果连接服务端时 cleanSession=1, 当 MQTT 客户端由离线(与服务端断开连接)再次上线时,离线期间发
给客户端的所有消息一律接收不到。
说白了,想接收离线消息, 客户端连接服务端时就必须使用 cleanSession=0;除了这个作用之外,如果cleanSession=0,则 MQTT 服务端会在客户端断开连接之后"记住" MQTT 客户端在线期间所订阅的所有"主题";也就是说,服务端会保存、存储客户端所订阅的主题。
如果 cleanSession=1,客户端既无法接收到离线消息、服务端也不会记住该客户端所订阅的主题, 服务端不会保存客户端的会话状态, 每次连接都是一次新的会话
下面再看看 MQTT 服务端接收到客户端发来的连接请求后所回复的 CONNACK 报文详细内容
CONNACK 报文
returnCode--连接返回码
当服务端收到了客户端的连接请求后,会向客户端发送 returnCode(连接返回码), 用来说明连接情况。如果客户端与服务端成功连接,则返回数字"0"。如果未能成功连接,返回码将会是一个非零的数字,具体这个数字的含义,请见下表
sessionPresent
在 cleanSession=0 的情况下, 当客户端连接到服务器之后, 可通过 CONNACK 报文中返回的sessionPresent 来查询服务端是否为客户端保存了会话状态(客户端上一次连接时的会话状态信息) , 如果服务端已为客户端保存了上一次连接时的会话状态,则 sessionPresent=1,如果没有保存会话状态,则sessionPresent=0。
如果 cleanSession=1, 在这种情况下,客户端是不需要服务端保存会话状态的, 那么服务端发送的确认连接 CONNACK 报文中, sessionPresent 肯定是 false(sessionPresent=0) ,也就是说,服务端没有保存客户端的会话状态信息。
简言之, CONNACK 报文的 sessionPresent 与 CONNECT 报文的 cleanSession 相互配合。其作用是客户端发送连接请求时,服务端告知客户端有没有保存会话状态。这个被服务端保存的会话状态是来自于上一次客户端连接时,譬如离线消息以及上一次连接时客户端所订阅的主题。
断开连接
当 MQTT 客户端连接到服务端之后,在后续的通信过程中,如果客户端想要断开与服务端的连接,此时客户端可以主动向服务端发送一个 DISCONNECT 报文来断开与服务端的连接,如下图所示
发布消息、订阅主题与取消订阅主题
PUBLISH-- 发布消息
当客户端连接到服务端之后,就可以向服务端发布消息了, 每条发布的消息必须指定一个"主题",表示向某主题发布消息,MQTT 客户端向服务端发布消息其实就是向服务端发送一个 PUBLISH 报文, 服务端收到客户端发送过来的 PUBLISH 报文之后,会向发送发回复一个报
SUBSCRIBE--订阅主题
客户端要想订阅主题,首先要向服务端发送主题订阅请求。客户端是通过向服务端发送 SUBSCRIBE 报文来实现这一请求的。该报文包含有一系列"订阅主题名"。请留意,一个 SUBSCRIBE 报文可以包含有单个或者多个订阅主题名。也就是说,一个 SUBSCRIBE 报文可以用于订阅一个或者多个主题。
另外每一个 SUBSCRIBE 报文还包含有"报文标识符"。报文标识符可用于对 MQTT 报文进行标识。不同的 MQTT 报文所拥有的标识符不同。 MQTT 设备可以通过该标识符对 MQTT 报文进行甄别和管理。当客户端向服务端发送 SUBSCRIBE 报文,服务端接收到 SUBSCRIBE 报文之后会向客户端回复一个SUBACK 报文(订阅确认报文),如下图所示:
UNSUBSCRIBE--取消订阅主题
客户端订阅了某一主题之后,可以随时取消订阅, MQTT 协议提供了这样的操作。
客户端通过向服务端发送一个 UNSUBSCRIBE 报文来取消订阅主题,当服务端接收到 UNSUBSCRIBE报文后,会向发送发回复一个 UNSUBACK 报文(取消订阅确认报文),如下图所示: