MQTT协议介绍

1. 简介

MQTT是建立在TCP/IP基础上的应用层协议,通过发布订阅的方式传输消息,在IOT领域有着广泛的应用。下边来介绍一些MQTT。

参考:hivemqmqtt简易入门

MQTT是CS架构,基本角色是Server与Client,MQTT的Server成为MQTT Breaker;MQTT的Client又有两种身份:Publisher与Subscriber,一个Client既可以只是Publisher,也可以只是Subscriber,亦或两者都是。如下图的汽车就即使Publisher又是Subscriber。

image-20220715093341867

与其他发布订阅工具类似,发布与订阅都是基于Topic的。

Topic的格式有如下几种:

  • 基础的分层结构,如:myhome/groundfloor/livingroom/temperature
  • 单层通配符+,如:myhome/groundfloor/+/temperature
  • 多层通配符#,如:myhome/groundfloor/#
  • 系统保留$,以$开头的主题用于broker进行内部统计目的,不会被Subscriber订

2. 基本流程

连接建立与断开

连接的建立,显示client发送一个CONNECT 消息给broker,然后broker响应一个应答CONNACK消息和状态码,基本过程如下:

来看看CONNECT消息:

  • clientId唯一标记一个client
  • cleanSession是否是持久session,下边介绍
  • username、password用于鉴权
  • lastWill等用于遗嘱,在client非正常断开时,broker会给相应主题发送遗嘱消息
  • keepAlive心跳

CONNACK就比较简单了,如下:

连接的断开是建立的逆操作,消息类型为 DISCONNECT。

消息的发布

由Publisher发送PUBLISH消息给Broker,然后Broker推送给Subscriber。

PUBLISH消息包括:

基本信息包括:packetId、topicName、payload

控制信息包括:qos、retainFlag、dupFlag,这些内容后边讲解

消息的订阅

client能接收到推送的前提是先进行订阅,结构如下,这里设计3种消息:SUBSCRIBE、SUBACK。

SUBSCRITE消息如下:

除了packetId,其余都是订阅的topic以及qos

SUBACK如下:

取消订阅

取消订阅是订阅的逆操作,过程与订阅相同,消息也类似,也涉及两种消息:UNSUBSCRIBE、UNSUBACK。

UNSUBSCRIBE不再需要qos

image-20220715100807054

UNSUBACK也是如此

image-20220715100834837

3. 数据包结构

MQTT的数据包分成3部分:

  • 固定头(Fixed header),存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识;
  • 可变头(Variable header),存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容;
  • 消息体(Payload),存在于部分MQTT数据包中,表示客户端收到的具体内容。

结构如下:

MQTT数据包结构

消息的类型包括前边的各种消息,总结如下:

MQTT消息类型

4. QoS

基本过程与报文很容易理解,下边需要深入探讨一下MQTT的特性,这些特性也是MQTT受欢迎的基础。

协议以及IM,首选要解决的第一个问题:如何保证消息不丢失?

想想TCP如何保证消息的可靠性?当然这个问题会比较大一些,最基本的就是通过对消息的ack、超时重传机制。

IM中如何保证的?对于单聊,理论上是对3个保证:1.保证server接收到了sender消息,2.保证receiver接收到server的消息;3. 保证receiver接收到了sender的消息;实际中考虑到效率与复杂度,以及与群聊的兼容,做到前两条即可;

MQTT怎么办呢?MQTT与IM尤其是群聊有类似的地方,但又与IM不同,IM的server清楚自己的receiver是谁,但MQTT却不知道,publisher并不在乎receiver是谁,它们的关系说白了都是跟broker的关系。在这种场景下,MQTT关注的问题就发生了变化,它首先关注消息不丢失问题,其次关注消息只送达一次问题。

它用QoS来综合应对这两个问题,QoS=0, 不保证数据不丢失;QosS=1,通过超时重传保证不丢失,但不保证消息只送达一次;QoS=2,保证不丢失,且保证消息只送达一次。这里分别来看看它们。

  • QoS 0

    没有应答,只发布一次,没用应用层的ack(TCP的ack还是有的),适应于对丢失数据容忍度高的场景。

  • QoS 1

    有了应用层的ack,也就有了客户端的超时重发。因为有了超时重发,也就不能保证接收端只收到一次,于是接收端就需要有去重功能。

    这应该是MQTT使用最多的方式,即保证了效率,也兼顾的品质。

  • QoS 2

    通过2个回合来确保Broker只收到1次,为什么需要两个回合呢

    接收端需要知道发送端已经接收消息;接收端需要知道对方已经知道它接收到消息了,且不会再重发该消息了。

    1. PUBLISH 发送消息
    2. PUBREC 告诉发送端,接收端已经收到,不需要再发了
    3. PUBREL 告诉接收端,发送端不会再发了
    4. PUBCOMP 完成

    它不是保证接收端只接收到了一次,如果第2步PUBREC丢失,发送端会重发该消息,这时候接收端其实会接收到2次。它保证的是接收端只处理一次,也就是只有收到PUBREL时,它才会处理消息。

    如果第4步PUBCOMP丢失会发生什么?

    发送端无法得知接收端是否收到第3步的确定信息,如果重发消息,又可能违背自己不再发送的承诺。最佳方式应该是重发PUBREL。

    这种方式的效率低,仅在协议层增加了保证,而QoS1在应用上即可完成该保证,性价比不高。

5. 消息存储相关特性

Persistent Session

在IM中,关键的问题之一:接收端不在线怎么办?在IM中通过持久化消息,再在上线后拉取的方式解决。在MQTT同样存着这个问题,来看看它如何处理的。

在连接建立的时候,就cleanSession的标志,意味着一个连接即可以是持久化的连接,也可以是非持久化的方式。

如果是非持久化的连接,那说明client并不在离线期间的消息,也就不需要存储。

如果是持久化的连接,那Broker就需要存储,具体存储的内容如下:

  • session是否存在
  • client所有的订阅(主题)
  • 所有没有被确认的Qos1、Qos2的消息
  • 所有离线期间,client错过的消息

加了持久化,使MQTT协议不太像协议,更像是系统了。

这里有几个注意点:

  1. 持久化是基于接收端的,与发送端无关

    持久化是在连接建立时指明的,如果接收端执行的持久化,而发送端非持久化,那会如何?broker同样会缓存这些消息;

    由于可以有多个接收者,对于同一主题,有的需要持久化,有的不需要,会如何?肯定是要缓存该主题的,但至于如何现实的,不是协议没有给出,需要实现关注。

  2. 消息会缓存多久的消息?

    协议并没有指明,这也要看实现,如果是一定要缓存,那可能要持久化到硬盘,一直保存;如果只是缓存一会,也可能缓存在内存里,超时清除。

在消息的存储基础之上,MQTT提供了两个特性:Retained message(以下称为保留消息)以及last will and testament(以下称为遗嘱)。

Retained message

保留消息针对的是首次订阅优化,当client订阅某个主题后,并不确定何时会收到消息,而这段时间,会让client处于一种未知状态,尤其是状态类的信息(开关的关闭)等等,这对应用的体验就不太好了。这样MQTT就提供了这样一种特性来解决上述问题。

一个保留消息就是PUBLISH时,retained flag被设置为true的消息。broker收到这些消息后,总是会存储最后一条保留消息。当接收client订阅与之匹配的主题(包括模糊匹配)后,会立即收到保留消息。

Last Will and Testament

遗嘱针对的是Client意外掉线的问题,有些时候IoT设备的网络环境不好,时断时连,如何优雅的处理这些非正常的掉线呢,MQTT提供了遗嘱特性来解决这个问题。

遗嘱,听名字就很形象了,首先提前写好的,其次是断线后的嘱托,这基本就是它的概括了。

连接的建立时,就立好遗嘱,如下lastWill相关的信息:

在哪个topic下,以什么样的Qos,发送什么message,那broker什么时候会发送呢?

  1. broker探测到与client连接的网络错误
  2. client没有发送DISCONNECT报文,就把TCP连接给关闭了
  3. client在Keep Alive时间段没有收到client的消息,下边就来看看Keep Alive

6. Keep Alive

MQTT如何发现Client连接掉线呢?答案是通过心跳机制。在IM中我们也看到了应用层的心跳机制,TCP不是可靠连接吗,TCP中不也有心跳机制吗?看看MQTT对这里的说明:有时TCP连接会变得不同步,这种未完成的连接被成为半开连接,连接的一端继续运行,而另一端却发生了故障。尤其是在移动场景中,这种TCP会话像是黑洞(树洞)。

通过心跳机制,broker与client能确保连接存在。

  • client发送PINGREQ包给borker,询问连接是否存活

  • broker回复PINGRESP包,告诉client连接依旧存活

7. 尾声

  1. 与IM完整模式相比,有何差异

    根本差异是:MQTT是client与server(broker)之间的关系,而IM是client与client之间的关系;

  2. client的连接与能耗问题

    如果client只是采集器,以固定时间频率上报数据,连接可以每次上传时创建,这样耗能就会降低;

    如果断路器或者能运动的终端,为了随时接收指令,就需要一直在线,耗能会高一些,每隔一段时间上报心跳;

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×