目录
一、前言
二、MQTT协议基本特点:
三、MQTT协议特性
四、MQTT协议通信模型
4.1 Client/Server架构
4.1.1 MQTT Client
4.1.2 MQTT Broker
4.2 发布/订阅模式
五、MQTT协议格式
六、MQTT协议实现原理
6.1 建立连接
6.2 关闭连接
6.3 发布与订阅
6.4 Qos
6.4.1 QoS0
6.4.2 QoS1
6.4.3 QoS2
6.5 keepalive与连接保活
6.5.1 keepalive的作用
6.5.2 keepalive特性
6.6 Retained消息
6.6.1 Retained消息的作用
6.6.2 Retained消息特点
6.6.3 Retained消息和持久会话
6.7 LWT(遗嘱)
七、MQTT版本
一、前言
MQTT协议可以说是目前应用最广的物联网应用层协议,MQTT解决了物联网中的一个最基础问题,即设备和设备、设备和云端服务之间的通信。主要应用场景是可以为大量低功耗、网络环境不可靠的物联网设备提供通信保障。该文章主要针对初学者的一些疑问进行解释,比如,你知道为什么QoS2等级的消息可以保证消息不会重复接收吗?为什么说是提供可靠的通信保障?更多协议细节没有展示,可以参考MQTT中文网。
二、MQTT协议基本特点:
-
实现简单
-
提供数据传输的QoS
-
轻量级、占用带宽低
-
可传输任意类型的数据
-
可保持的会话(Session)
三、MQTT协议特性
-
基于TCP长连接的应用层协议,;
-
基于Client/Server架构;
-
使用订阅/发布模式,将消息的发送方和接收方解耦;
-
提供3种消息的QoS(Quality of Service): 至多一次、最少一次、只有一次;
-
收发消息都是异步的,发送方不需要等待接收方应答;
四、MQTT协议通信模型
4.1 Client/Server架构
完整的MQTT协议包括两部分,MQTT Client和MQTT Server,其中MQTT Server 也就是Broker;
4.1.1 MQTT Client
只要设备基于MQTT协议连接了MQTT Broker ,就认为这个设备是MQTT Client, MQTT Client 可以单独作为发布者和订阅者,也可以同时是发布者和订阅者。
4.1.2 MQTT Broker
MQTT Broker 是MQTT协议的核心,主要作用是接收发布者的消息,然后转发给对应的订阅者。Broker可以对Clinet接入进行授权,并对Client进行权限控制。常用的C语言编写的MQTT Broker 开源库有Mosquitto,更多可以查看https://github.com/mqtt/mqtt.github.io/wiki/servers。如果学习MQTT需要Broker环境,可以通过这些开源库自建,也可以用各大云平台提供的Broker服务,如阿里云、腾讯云。
4.2 发布/订阅模式
MQTT是通过发布/订阅模式实现,由于发布方和订阅方没有直接联系,所以需要一个中间方对消息进行发布和存储,我们称这个中间方为Broker,Broker 简单来说就是基于mqtt协议的server实现(github 上有很多开源实现),连接到Broker的发布方和订阅方作为Client;
-
发布方和订阅方都建立了到Broker的TCP连接;
-
订阅方告知Broker它要订阅的Topic;
-
发布方将消息发送到Broker,并指定消息主题(Topic);
-
Broker接收到消息后,检测哪些订阅方订阅了对应的Topic,然后将消息发送到订阅方;
-
订阅方从Broker获取消息;
-
如果某个订阅方处于离线状态,Broker可以先保存对应的消息,当订阅方下次连接到Broker的时候,再将之前的消息发送给订阅方;
五、MQTT协议格式
MQTT协议使用二进制数据包,包含三个部分,分别是固定头,可变头、消息体;
-
固定头:存在于所有的MQTT数据包中,长度是2~5字节,包括3部分内容,数据包类型(4bit),标识位(4bit),数据包剩余长度大小(1~4Byte),具体含义参考MQTT协议
-
可变头:部分MQTT数据包包含
-
消息体:部分MQTT数据包包含
MQTT协议中,固定头中的数据包剩余长度大小包括可变头和消息体长度,剩余长度可用1~4Byte表示,4Byte最大可表示256MB(0xFFFFFF7F)。有些数据包没有可变头和消息体,比如PINGREQ数据包只有2Byte,由此可以计算MQTT数据包的最大值和最小值
-
最大值:256MB+5Byte,其中256MB是剩余长度最大值,包括可变头和消息体;5Byte是固定头,数据包类型(4bit),标识位(4bit),数据包剩余长度大小(4Byte);
-
最小值:2Byte,比如PINGREQ数据包,只有固定头,没有可变头,消息体;
六、MQTT协议实现原理
6.1 建立连接
Client在可以发布和订阅消息之前,必须先连接到Broker,这个连接不仅仅是建立TCP连接,也包括应用层建立连接,MQTT建立连接流程如下:
-
Client向Broker发送CONNECT数据包;
-
Broker收到CONNECT数据包后,如果允许接入则回复返回码为0的CONNACK数据包,如果拒绝接入,则回复返回码非0的CONNACK数据包,返回码表示失败原因;
6.2 关闭连接
关闭连接可以由Client或Broker任意一方发起,也有第3种情况会关闭连接,就是心跳超时,不同的关闭流程也有区别:
-
Client端关闭连接:Client向Broker发送DISCONNECT数据包,不用等待Broker回复DISCONNECT数据包,Broker不会回复,发完就可以关闭底层的TCP连接;之所以要在断开TCP连接前发送与Broker没有交互的数据包,是为了让Broker识别是正常的断开的连接,Broker会丢弃当前连接的遗愿消息,否则会识别为非正常连接,不会丢掉当前连接的医院消息;
-
Broker端关闭连接:不需要发送任何MQTT数据包,直接关闭底层TCP连接
-
keepalive超时:MQTT协议规定Broker没有收到Client的DISCONNECT数据包之前都应该保持连接,但是如果Broker在keepalive的时间间隔里,没有收到Client的任何数据包时就会主动关闭连接。这个keepalive 的数值是Client在发起连接时数据包种指定的;
6.3 发布与订阅
发布与订阅的概念想必大家都很了解,具体交互的协议数据包可查看MQTT协议,介绍几个关键特性:
-
支持接收离线消息:MQTT支持订阅者接受离线消息,但并不意味着离线状态下可以接收消息,意思是如果发布者在订阅者处于离线状态下时向Broker发布了消息,订阅了对应Topic的订阅者再次连接到Broker后会收到发布方在其离线期间发布消息,接收离线消息的要求是需要Client使用持久会话,且发布时的QoS不小于1;
-
主题通配符:发布和订阅的消息主题可以使用通配符,通配符的主要作用是指定消息主题名称的层级,有一个特殊情况,取消订阅时通配符不起作用,取消订阅的主题名称必须每个字都和订阅时指定的主题名称相同;
6.4 Qos
MQTT协议设计初衷是提供一套保证消息稳定传输的机制,用于网络带宽窄,信号不稳定环境下的数据传输,这套机制包括消息应答,存储和重传。在这套机制下提供了三种不同层次的QoS(Quality of Service),QoS是Sender和Receiver之间达成的协议,而不是Publisher和Subscriber之间达成的协议。也就是Publisher和Broker之间的QoS,Subscriber和Broker之间的QoS ,两者是没有直接关系的。
-
QoS0:至多一次
-
QoS1:至少一次
-
QoS2:确保只有一次
以上是3种QoS服务层次,很多初学者在学习MQTT初期可能不太明白实现原理,尤其是QoS2如何确保只发送一次的,其实从MQTT协议本身来分析是很好理解的;
6.4.1 QoS0
Qos0是最简单的一个消息质量服务等级,Sender向Receiver发送含消息数据的PUBLISH数据包,然后不管结果如何,直接丢弃已发送的PUBLISH数据包,所以Receiver最多收到一次。
6.4.2 QoS1
为了保证Receiver至少收到一次消息,QoS1增加了应答机制;Sender向Receiver发送一个带有消息数据的PUBLISH数据包,并把这个数据包保存在本地,Receiver收到后会返回一个PUBACK数据包,PUBACK数据包的可变头中有一个和PUBLISH数据包相同的包标识,Sender收到PUBACK后根据包标识(Packet Identifier)找到本地保存的数据包并丢弃,发送完成。
如果Sender发送PUBLISH数据包后一定时间内没收到PUBACK数据包,就会把PUBLISH数据包中的DUP标识置1(表示数据包是重发)重新发送,重复以上步骤,直到收到PUBACK,所以如果因为网络延迟等原因导致超时时间内Receiver没有收到PUBLISH数据包或PUBACK没发出去,就会导致Receiver收到多次。
6.4.3 QoS2
QoS2不仅要保证Receiver能收到Sender发送的数据包,而且不能重复,即只能收到一次,所以重传和应答机制更加复杂;在3种服务等级中,QoS2是开销最大的,速度最慢的,当然也是最安全的。
QoS2用2套请求/应答流程,也就是一个4段的握手来保证Receiver只收到一次Sender发送的消息。
1)Sender发送QoS值为2的PUBLISH数据包,假设数据包中包含Packet Identifier(包标识)为P,并在本地保存PUBLISH数据包;
2)Receiver收到PUBLISH数据包后,在本地保存PUBLISH数据包的Packet Identifier 为P,并回复Sender一个PUBREC数据包,PUBREC包含Packet Identifier为P,但是没有消息体;
3)Sender收到PUBREC后就可以丢掉Packet Identifier为P的PUBLISH数据包,同时保存PUBREC数据包到本地,并回复Receiver一个PUBREL数据包,PUBREL数据包中的Packet Identifier为P,没有消息体;如果Sender超时时间内没有收到PUBREC数据包,会把PUBLISH中的DUP标识 设为1,重新发送PUBLISH数据包;
4)当Receiver收到PUBREL数据包时,可以丢掉保存在本地的 Packet Identifier P,并回复Sender一个PUBCOMP数据包,PUBCOMP数据包可变头中的Packet Identifier为P,没有消息体。
5)当Sender收到PUBCOMP数据包,数据包传输完成,丢掉本地的PUBREC数据包。如果Sender在超时时间内没有收到PUBCOMP数据包,就会重复发PUBREL数据包;
以上就是完成一个QoS2等级数据包的流程,至少要发送4个数据包,但是有同学在第3步发现,当Sender没有收到Receiver的PUBREC数据包时,仍然会出现PUBLISH数据包重复发送的情况,那么怎么保证Receiver只收到一次的呢?
原因就是Receiver收到PUBLISH数据包时,不是立即投递给协议上层,而是在本地做持久化将消息保存起来,等收到PUBREL数据包后才将消息投递给协议上层,然后把本地保存的Packet Identifier P删除,由于PUBREL数据包中没有Packet Identifier P,所以即使PUBCOMP没有发送成功导致PUBREL重发,Receiver也只需要回复PUBCOMP,不用重复投递PUBLISH数据包到协议上层;
QoS2服务等级实现原理可以查看这篇博客,写的很详尽:https://blog.csdn.net/zerooffdate/article/details/78950907
6.5 keepalive与连接保活
6.5.1 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的约定和网络状态监测;
6.5.2 keepalive特性
-
如果一个Keepalive时间间隔内,Client和Broker有过数据包传输,Client就没有必要再使用PINGREQ数据包了;
-
Keepalive的值时有Client在发送CONNECT数据包时指定,不同的Client可以指定不同的值;
-
Keepalive的最大值是18时12分15秒
-
Keepalive 的值为0时代表不使用Keepalive机制
6.6 Retained消息
6.6.1 Retained消息的作用
会有这样一种场景,Publisher发布了一个消息后,Subscriber订阅了这个主题,那么这个 Subscriber就不会收到在它订阅之前Publisher发布的消息。 Retained消息就是为了解决这个问题,Retained消息是指在PUBLISH数据包中将Retained标识设为1的消息,Broker收到这样的PUBLISH数据包后,将会为该主题保存这个消息,当一个新的订阅者订阅该主题时,Broker会将这个消息发送给订阅者。
6.6.2 Retained消息特点
-
一个Topic只有一个Retained消息,发布新的Retained消息将覆盖旧的Retained消息;
-
如果订阅者使用通配符订阅主题,会收到所有匹配主题的Retained消息;
-
只有新的订阅者会收到Retained消息,如果订阅者重复订阅一个主题,会被当做新的订阅者收到Retained消息;
-
向主题发布一个payload长度为0的Retained消息就可以删除这个主题的Retained消息;
6.6.3 Retained消息和持久会话
两者没有关系,Retained消息是Broker为每一个主题单独存储的,而持久会话是Broker为每一个Client单独存储的;
6.7 LWT(遗嘱)
LWT全称为Last Will and Testament,也就是遗愿,包括遗愿主题,遗愿QoS,遗愿消息等,具体见MQTT协议,遗愿具有以下特点:
-
遗愿的相关设置是建立连接时,在CONNECT数据包里面指定的;
-
遗愿用于非正常断开连接的情况,当Broker检测到Client非正常的断开连接时,就会向Client的遗愿主题发布一条消息;
-
Client发布DISCONNECT数据包断开连接,属于正常断开,不会触发LWT机制,而且Broker会丢掉Client连接时指定的LWT参数
七、MQTT版本
以上都是基于MQTT 3.1.1, MQTT 5.0更多内容参考官方文档。