一、MQTT协议基本介绍
1、简介
MQTT协议是一种轻量级的、基于发布-订阅(Publish-Subscribe)模型的通信协议。它通常用于设备之间的即时通讯,特别适用于物联网领域。基于发布订阅模型的含义: 在这个模型中,发布者和订阅者之间解耦,发布者无需直接知道订阅者的存在
它由IBM在1999年发布。它的最大优点在于:可以用很少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,在物联网、小型设备、移动应用等方面有较广泛的应用。
2、模型
MQTT的运作模型中,有三种角色:发布者、服务器和订阅者,其中发布者和订阅者可以身份互换。通信是通过一个消息代理(Broker)来进行。消息代理负责接收发布者发送来的消息,并将这些消息传递给订阅了此发布者的订阅者。
在实际应用中,担任服务器的角色可以是一个小型的计算机或者是一个大型的Internet服务器都行。下图就是一个简单的MQTT模型:
二、MQTT协议的数据包结构
MQTT数据包分为三个部分,分别是固定报头、可变报头、有效负载(消息体)。
前两个字节是固定报头,后面的可变报头和有效负载的字节数由固定报头中的一些位来决定。接下来在介绍数据包结构的各个部分时会以connect(连接)报文为例子进行介绍,设备与服务器进行通信的第一条报文就是connect报文。
1、固定报头
固定报头存在于所有的类型的数据包中,占两个字节,第一个字节由控制报文类型、DUP标志、服务质量等级、RETAIN标志组成。控制报文类型如下图所示一共16种,占4比特。dup标志占1比特,用于指示当前消息是否是一个重复的消息。如果DUP标志被设置为1,表示这是一个之前已经发送过的相同消息的重发。然后是服务质量等级,用于确定消息传递的可靠性,在后面会详细介绍它。第一字节的最后一位是retain标志,retain标志用于指示服务器是否应该保留最后发布的消息,并在新订阅者连接到主题时将其发送给新的订阅者。
下面这张图给出了在各个控制报文中这四个标志位(DUP、QOS、retain)应该是什么值,只有publish报文可以自己设置,其余的控制报文这几位都是确定的。第二个字节是剩余长度,它表示可变报头和有效负载的总长度,要等可变报头和有效负载都确定之后才能确定剩余长度的值。
******************这里详细介绍一下固定包头中的消息服务质量等级:
一共分为三个等级,占两个字节,设置时不能设置成两位都是1,否则服务器会当作是非法命令,就会断开和客户端的连接。
①首先是等级0,此时消息最多只发送一次,发布者不会管消息是否送到了订阅者处:
②其次是等级1,消息至少发送到订阅者处一次,能确保消息送到,但可能会多发:
③最后是等级2,最高等级,能确保消息精确送达一次,但消耗的资源也是最多的:
这里简单讲解一下等级1的流程:首先发布者发布消息,服务器将消息转发给订阅此发布者的设备,此设备接收到消息之后回复一个puback信号,服务器再将此信号发回发布者,发布者接受到这个信号之后就判定消息已经送达给了设备,若是在设定时间内没有收到回复,则发布者会再次发送消息直至收到应答信号)(发布者靠什么知道是是不是订阅者发送的puback:当qos等级大于0时,一些报文的可变报头中就需要包含一个消息标识符,就是用于确认消息是否来自自己的订阅者或者发布者的),由于connect控制报文的序号值是1,其余四个标志位都为0(往上翻一页右边那个图),剩余长度还不知道,所以固定报头的报文为:10 xx。
2、可变报头
并不是所有控制报文都包含可变报头的,下面的表格就给出了包含可变报头的报文类型,可以看到connect报文的可变报头包含协议名,协议级别,连接标记,心跳时长。
值得注意的是一些报文的可变报头中会包含消息标识符,比如图中的这几个,而publish报文只有当qos>0时才包含,比如在上一页提到的qos级别中,在等级1下,发布者发送一条PUBLISH报文,会在可变报头中包含一个16位的消息标识符。接收到消息的订阅者,会向发布者发送一个PUBACK消息,其中包含相同的消息标识符,以通知发布者消息已经成功接收。
由上面的图知CONNECT报文包含四个字段:协议名(6字节),协议级别(1字节),连接标记(1字节)和心跳时长(2字节)。协议名和协议级别是固定的。
①协议名如下图:
②MQTT 3.1.1的协议级别是4。
③连接标志如下图:
连接标志一共8比特,简单介绍一下:
User Name Flag 第七位为1则表示有效负载中需要包括用户名
Password Flag 第六位为1则表示有效负载中需要包括用户名对应的密码
Will Retain 第五位指示服务器是否应该保留最后发布的遗愿消息。
Will QoS 第四位和第三位是遗愿消息的服务质量级别。
Will Flag 第二位用于标记是否发送遗愿消息,遗愿消息是指当客户端非正常断开时,客户端希望发送一条消息给一个指定的topic,通知对方自己掉线了。
Clean Session 第一位表示清理会话标志(这位为1时客户端和服务端必须丢弃之前的任何会话并开始一个新的会话)
Reserve 最低位是保留位,是为了向前兼容性和未来协议版本的扩展留出空间。
这里关于遗愿方面的位都设置为0,比较方便。
④心跳时长:
指客户端传输完成一个控制报文到发送下一个报文的时刻,两时刻之间允许空闲的最大时间,按需求设定,这里设置为60秒,如果超过这个时间没有发送下一个报文的话,客户端就会发布一个心跳请求报文,来确认和服务器的连接是否活跃。
至此可变报头的报文也构建出来了,可变包头的长度一共十位:[10 xx][00 04 4d 51 54 54][04][C2][00 3C]。(这里的进制转换直接参考ascii表,60的十六进制对应0x3C)。
3、有效载荷
有效负载,也就是实际的消息体,也只存在于部分的控制报文中,如下图所示,可以看到connect报文是包含有效负载的,内容是:Client ID/[Will Topic]/[Will Message]/[User Name]/[Password](客户端ID/遗愿主题/遗愿消息/用户名/密码)顺序是不可变的,除了客户端id之外,其它内容都是可选的,由可变报头中的连接标志决定。
当可变报头中连接标记第二位Will Flag为1时,也就是要发送遗愿消息时,有效负载中才会出现Will Topic(遗愿主题)和Will Message(遗愿信息)。由于这里设置了Will Flag为0,所以只有客户端ID,用户名,密码:
有效负载顺序依次为ClientID【7字节】:[00 05 41 42 43 44 45]、UserName【12字节】:[00 0A 30 30 30 30 30 30 30 30 30 30]、PassWord【12字节】:[00 0A 31 31 31 31 31 31 31 31 31 31]。有效载荷总的报文字节数为【31字节】。
加上可变报头【10】字节,固定报头中的剩余长度为41字节。总的报文即:10 29 00 04 4d 51 54 54 04 C2 00 3C 00 05 41 42 43 44 45 00 0A 30 30 30 30 30 30 30 30 30 30 00 0A 31 31 31 31 31 31 31 31 31 31。CONNECT报文的整体数据包结构:10 [Remaining Length] [Protocol Name][Protocol level][Connect Flags] [Keep Alive ] [Payload]。
三、报文构建再举例:PUBLISH报文
这里将publish报文的构建过程也介绍一下,publish报文也是最常用的报文之一。第一部分固定报头,publish控制报文的序号值是3,其余四个标志位和connect报文一样都设置成0,所以固定报头的报文是:30 xx。第二部分可变报头,从可变报头那部分的第一个图中可以看到,服务质量等级为0的publish报文的可变报头只有主题名,这里主题名的格式与connect报文的协议名称格式一样,高二位是长度其余位就是主题名,如下图:
这里设置主题名为/pa,都是字符类型,对应的16进制可以查表,加上主题名之后的报文为[30 xx][00 08 2f 70 61],可变报头的字节数为5。第三部分是有效载荷,对于publish报文这就包含了要发送的具体数据,这里设定数据为{“Humi”:60},是json数据格式(JSON是一种轻量级的数据交换格式,JSON 数据由键值对组成,键值对之间用逗号分隔,整个数据被包含在花括号 {} 中。键值对的键是一个字符串,值可以是字符串、数字、布尔值、数组、对象或 null):对应的16进制也是查表,有效载荷的字节数为11,则剩余长度就是11+5=16,对应16进制为0x10,则publish控制报文就构建出来了:[30 10 00 08 2f 70 61 7B 22 48 75 6D 69 22 3A 36 30 7D]。
PUBLISH报文整体数据包结构:30 [Remaining Length] [Topic Name][Message]。