文章前注:本文的大量素材来自于百度百科和MQTT协议3.1.1中文板
MQTT协议3.1.1中文版网址:http://blog.mcxiaoke.com/mqtt/protocol/MQTT-3.1.1-CN.html
一、认识MQTT
-
MQTT(消息队列遥测传输) 是ISO 标准(ISO/IEC PRF 20922)下基于 发布/订阅 范式的消息协议。它工作在TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议。
简而言之,MQTT其实就是一个用于TCP通信的消息协议而已。既然消息协议,“消息” 自然是表示MQTT其实本质就是消息,消息在某种理解上,可以理解报文,报文包,数据等等;既然是协议,自然表示它有着自己的规则,什么叫规则?就是规定这个消息该以怎样的固定格式(详见二 协议内容)去传输。 -
MQTT既然发布,自然是会有它实际应用的,MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议
-
MQTT协议主要特性:
①使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
②对负载内容屏蔽的消息传输;
③使用 TCP/IP 提供网络连接;
④有三种消息发布服务质量:小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
⑤使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制这些特性都是什么意思呢?不用管,先接着往下看,最后再来看它的特性…
二、MQTT协议通信过程概述
首先,MQTT是利用TCP通信协议的,那么它必然是 有序、可靠、面向连接、双向传输的
其次,MQTT是通过交换预定义的MQTT控制报文来通信(控制报文详见三 协议控制报文)
协议流程如下:
对MQTT协议进行概述:
第一步:
1.网络建立连接后,如果服务器在合理的时间内没有收到CONNECT报文,那么服务端必须断开原有的客户端连接
2.在收到CONNECT报文后,服务端进行验证,如果CONNECT报文不符合规范,则服务端不会发送CONNACK报文,且直接关闭网络连接
3.服务端验证CONNECT报文规范,其中就有验证身份和授权的用户名和密码 ,如果有一项不符合规范,则直接关闭网络连接
4.服务端如果验证成功,则会创建会话(会话可以看做通信内容,详见三 清理会话标志)
5.会话创建成功,服务端发送CONNACK报文作为CONNECT报文的确认响应
6.开始消息的分发和保持连接状态的监测
第二步:发布和订阅
如果是发布
1.客服端使用 PUBLISH报文 发送应用消息给服务端,目的是分发给其他订阅了相同主题的客户端,也就是服务端其实是个中转的地方
2.服务端使用 PUBLISH报文 发送应用消息给每一个订阅匹配的客户端(*这里与其说使用,说转发会更好,服务端存储客户端A发布的应用消息,如果客户端B订阅该主题,那么服务端就会发送PUBLISH报文给客户端B)
3.收到一个PUBLISH报文时,接受者(可能是客户端,也可能是服务端)的动作取决于Qos等级,根据等级发送不同的回应报文(详见三 PUBLISH报文)
如果是订阅
1.服务端收到客户端发送的一个SUBSCRIBE报文时,会发送SUBACK报文响应
2.允许服务端在发送SUBACK之前就开始发送与订阅匹配的PUBLISH报文,如果服务端收到的SUBSCRIBE报文的主题过滤器与一个现存的主题过滤器相同,那么服务器就会使用新的订阅去替换现存的订阅
3.如果服务端接收到的主题过滤器不同于任何现存订阅的过滤器,服务端就会创建一个新的订阅并发送所有匹配的保留消息
4.服务端发送给客户端的SUBACK报文对每一对主题过滤器和Qos等级都必须包含一个返回码,这个返回码必须表示那个订阅被授权的最大Qos等级或者订阅失败
三、MQTT协议控制报文
在介绍MQTT控制报文前,了解两个东西,一是控制报文的结构,一是控制报文有哪些类型
MQTT控制报文结构 = 固定报头 + 可变报头 + 有效载荷
注意,所有的控制报文都有固定报头,但不一定有可变报头和有效载荷
控制报文类型有如下:
接下来就详细介绍各个控制报文的结构:
之前提到过,
控制报文 = 固定报头 + 可变报头 + 有效载荷
固定报头共2个字节,一个字节占8位。第一个字节前4位是用于指定控制报文类型的标志位,后4位是控制报文类型;第二个字节是剩余长度。
剩余长度表示当前报文剩余部分的字节数,包括可变报头和有效负载。
*看到这里有人可能就会疑惑,为什么固定报头的剩余长度会包括可变报头和负载呢?这样的话是不是就表示 可变报头和负载其实只占一个字节?
*其实不是的,固定报头的第二个字节只是单纯的表示了 可变报头+有效负载的长度,而可变报头和负载最大可以占四个字节
*简而言之就是,剩余长度本身占一个字节,是属于固定报头的,只是这一个字节的含义是 可变报头和负载的长度
控制报文类型标志位的4位具体数值为:
可变报头同样占2个字节,2个字节共同作用表示报文标识符,第一个字节表示报文标识符的最高有效字节MSB(most significant bit),第二个字节表示报文标识符的最低有效字节(least significant bit)
*那么什么是报文标识符呢,最高有效位和最低有效位是多少呢?
当客户端或服务端发送以下新的报文类型时,必须分配一个未使用的报文标识符,不同的控制报文有不同的报文标识符(*可以理解为以下各个报文都有着自己的专属报文标识符,这个标识符是一个16位的值,如0x1234,这个值是可以自己定义的)
如果 客/服 重发旧的报文时,必须使用相同的报文标识符(*可见报文标识符一旦分配,是不会再去改变值的,只会释放),在发送时分配,在收到回应后释放。
含有可变报头,即报文标识符的控制报文 |
---|
PUBLISH |
PUBACK |
PUBREC |
PUBREL |
PUBCOMP |
SUBSCRIBE |
SUBACK |
UNSUBSCRIBE |
UNSUBACK |
1. CONNECT控制报文
客户端与服务端建立连接后,客户端发送给服务端的第一个报文,一旦建立连接之后,CONNECT报文只会被发送一次。
固定报头
可变报头
CONNECT控制报文的可变报头包含四个字段:协议名,协议级别,连接标志和保持连接
(1)协议名
(2)协议级别
协议级别的字段的值是0x04
(3)连接标志
Reserved:保留标志,该标志为0表示连接验证通过,为1时表示断开客户端连接
Clean Session:清理会话标志,指定了会话状态的处理方式,该标志为0时表示 服务端必须创建一个新的会话(如果没有会话)或者恢复当前的会话(如果有会话,*会话可以理解为与客户端成功建立连接)。并且在之后断开连接后,客户端和服务端都必须保存会话信息;该标志为1时,客户端和服务端必须丢弃之前的任何会话并开始一个新的会话,且这个新的会话信息是不能被之后任何一个会话重用的。
*简而言之,清理会话标志设置为 1 的客户端不会收到旧的应用消息,而且在每次连接成功后都需要重新订阅任何相关的主题。清理会话标志设置为 0 的客户端会收到所有在它连接断开期间发布的 QoS 1 和 QoS 2 级别的消息。因此,要确保不丢失连接断开期间的消息,需要使用 QoS 1 或 QoS 2 级别,同时将清理会话标志设置为 0。
Will Flag:遗嘱标志,当服务端检测到了一个I/O错误或者网络故障;或者客户端在保持连接的时间内未能通讯;或者客户端没有先发送DISCONNECT控制报文直接关闭了网络连接;或者由于协议错误服务端关闭了网络连接 等情况时,才会将 Will Flag 置1,并请求服务端发布这个遗嘱消息
该标志置1表示如果连接请求被接受了,遗嘱消息必须被存储在服务端,待网络连接关闭时,服务端必须发布这个遗嘱消息。
如果服务端接收到了客户端发送DISCONNECT报文,那么这个遗嘱消息就会被删除,而不会发布
该标志置0时,Will Qos 和 Will Retain 字段必须也置0,表示网络连接断开时,不能发送遗嘱消息
Will Qos:发布遗嘱消息时使用的服务质量等级(Qos)。当 Will Flag 被置0时,Will Qos 也置0;当Will Flag 被置1时, Will Qos可以置为 0,1,2
最多分发一次:消息发布一次,接收端最多能收到一次。*收到就收到,收不到就算了
至少分发一次:消息发布至少一次,确保接收端必定能收到一次,但因为多次发送,有可能会造成重复。*一定给你完整送到一次,如果有多余的,权当送你了,但是后果自负的哈
只分发一次:消息发布一次,各种机制作用,最后确保接收端收到一次且只有一次。*确保你无重复无缺失的收到消息
Will Retain:遗嘱保留。当Will Flag 置0时, Will Retain只能置0;当Will Flag 置1时,Will Retain置0表示服务端必须将遗嘱消息当作非保留消息发布,Will Retain置1表示服务端必须将遗嘱消息当作保留消息发布
Password Flag:密码标志,置0表示有效载荷中不能包含密码字段;置1表示有效载荷中必须包含密码字段
User Name Flag:用户名标志,置0表示有效载荷中不能包含用户名字段;置1表示有效载荷中必须包含用户名字段
(4)保持连接
*保持连接是理解心跳报文的重中之中,也是写代码最核心的部分
保持连接是一个以秒为单位的时间间隔,16位(最大设定为16位全1,即18个小时),表示在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔。
客户端负责保证 控制报文 发送的时间间隔不超过设定的保持连接的值,如果没有任何其他的控制报文可以发送,则达到时间间隔尽头,发送PINGREQ控制报文(*就是心跳请求控制报文,当然也可以不在时间间隔尽头发送,只是几乎所有的项目代码都设定为时间间隔尽头而已)
如果客户端发送了PINGREQ报文之后,如果在设定的心跳回应时间间隔内没有收到PINGRESP报文(心跳回应报文),它应该关闭与服务端的连接
有效载荷
CONNECT控制报文的有效载荷字段长度,首先要看 可变报头的标志 是否包含这些字段,如果包含的话,有效载荷就有:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码
(1)客户端标识符
客户端标识符是服务端用于识别客户端的。连接服务端的每一个客户端都有唯一的客户端标识符。
客户端标识符必须存在且必须是CONNECT控制报文的有效负载的第一个字段
(2)遗嘱主题
如果遗嘱标志 Will Flag 被设置为1,则有效载荷的下一个字段是遗嘱主题
(3)遗嘱消息
如果遗嘱标 Will Flag 被设置为1,则有效载荷的下一个字段是遗嘱消息。遗嘱消息定义了将被发布到遗嘱主题的应用消息
(4)用户名
如果用户名标志被设置为1,有效载荷的下一个字段就是它,服务端可以将它用于身份验证和授权
(5)密码
如果密码标志被设置为1,有效载荷的下一个字段就是它,服务端可以将它用于身份验证和授权
2.CONNACK报文
服务端发送CONNACK报文响应从客户端收到的CONNECT报文。服务端发送给客户端的第一个报文必须是CONNACK报文。CONNACK报文只有固定报头和可变报头。
如果客户端在设定的保持连接时间段内没有收到服务端的CONNACK报文,客户端应该关闭网络连接
固定报头
可变报头
由连接确认标志和连接返回码组成
(1)SP:当前会话标志 Session Present,当服务端收到CONNECT报文中的清理会话标志为1的连接时,CONNACK报文中的返回码置0,SP置0
当服务端收到CONNECT报文中的清理回话标志为0的连接时,如果服务端已经有保存的客户端的会话状态(*即清理会话标志为1,会删除已有的会话),则CONNACK的SP置1;如果服务端没有已保存客户端的会话状态(*即清理会话标志为0,会保存当前的会话状态),则CONNACK的SP置0,返回码置0
SP使服务端和客户端在是否有已存储的会话状态上保持一致
(2)连接返回码:如果服务端收到一个合法的CONNECT报文,但出于某些原因无法处理它,服务器就会尝试发送一个包含非零返回码的CONNACK报文,客户端接收到这个回应报文后会关闭网络连接;如果CONNECT报文合法且没有其他错误,则服务端会发送一个包含 零值返回码的CONNACK报文给客户端,从而实现会话的接通
3.PUBLISH控制报文
PUBLISH控制报文是指客户端向服务端或者服务端向客户端传输一个应用消息
固定报头
(1)DUP:重发标志,如果该标志被置0,表示这是客户端或服务端第一次请求发送这个PUBLISH报文。如果该标志被置1,表示这可能是一个早前报文请求的重发
客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志置1
(2)RETAIN:保留标志
如果客户端发给服务端的PUBLISH报文中的 RETAIN 标志被置1,服务端必须存储这个应用消息和它的服务质量等级(Qos),以便它可以被分发给未来的主题名匹配的订阅者。倘若Qos为0,即最多分发一次,那么服务端必须丢弃之前为那个主题保留的任何消息,而将这个新的Qos 0 的消息作为那个主题的新保留消息。
如果客户端发送给服务端的PUBLISH报文的 RETAIN 标志被置0,服务端不能存储这个消息也不能移除或替换任何现存的保留消息
可变报头
PUBLISH的可变报头包含主题名和报文标识符
(1)主题名:用于识别有效载荷数据应该被发布到哪一个信息通道
(2)报文标识符:只有当Qos等级是 1 或 2 时,报文标识符字段才会出现在PUBLISH报文中
有效载荷
有效载荷包含将被发布的应用消息。数据内容和格式是应用特定的 。
PUBLISH报文的接受者必须按照 PUBLISH报文中的Qos等级发送响应
4.PUBACK控制报文
PUBACK报文是对 Qos 1 等级的PUBLISH报文的响应。PUBACK没有有效载荷。
固定报头
可变报头
5.PUBREC控制报文 → PUBREL控制报文 → PUBCOMP控制报文
这三个报文是对 Qos 2 等级的PUBLISH报文的响应,响应顺序如题
所有的回应控制报文都没有有效载荷
(1)PUBREC控制报文
固定报头
可变报头
(2)PUBREL控制报文
固定报头
PUBREL控制报文固定报头的保留位必须是0010,否则服务端将会认为其不合法从而关闭网络连接
可变报头
(3)PUBCOMP控制报文
固定报头
可变报头
6.SUBSCRIBE控制报文
客户端向服务器发送SUBSCRIBE报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。为了将应用消息转发给与那些订阅匹配的主题,服务器发送PUBLISH报文给客户端。SUBSCRIBE报文为每个订阅指定了最大的Qos等级,服务端根据这个发送应用消息给客户端
固定报头
SUBSCRIBE控制报文固定报头的保留位必须为0010,服务端收到其他值均不合法,都会断开连接
可变报头
有效载荷
SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表,它们表示客户端想要订阅的主题。
SUBSCRIBE报文的有效载荷必须包含至少一个主题过滤器和 Qos等级字段。
以下举个栗子
如果有效载荷是下面这样
那么整个有效载荷的格式是下面这样
以上是个栗子
7.SUBACK控制报文
服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文
SUBACK报文包含一个返回码清单,它们指定了SUBSCRIBE请求的每个订阅被授权的最大Qos等级
固定报头
可变报头
有效载荷
有效载荷包含一个返回码清单。每个返回码对应等待确认的SUBSCRIBE报文中的一个主题过滤器。返回码的顺序必须和SUBSCRIBE报文中主题过滤器的顺序相同
(8)UNSUBSCRIBE控制报文
客户端发送 UNSUBSCRIBE报文给服务端,用于取消订阅主题
固定报头
UNSUBSCRIBE报文固定报头的保留位必须是0010,否则服务端判定其不合法
可变报头
有效载荷
UNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。
(9)UNSUBACK控制报文
服务端发送UNSUBACK报文给客户端用于确认收到UNSUBSCRIBE报文,其没有有效载荷
固定报头
可变报头
(10)PINGREQ控制报文
没有可变报头和有效负载
固定报头
(11)PINGRESP控制报文
服务端发送PINGRESP报文响应客户端的PINGREQ报文。表示服务端还活着,保持连接处理中就用到了这个报文,其也没有可变报头和有效负载
固定报头
(12)DISCONNECT控制报文
DISCONNECT报文是客户端发送给服务端的最后一个控制报文,表示客户端正常断开连接。该报文没有可变报头和有效负载。
固定报头
服务端必须验证保留位为0000,如果不为0必须断开连接(*这里的连接是异常断开的)
以上MQTT协议就介绍到这啦~