1、报文结构
在MQTT协议中,一个MQTT数据包由:固定头(Fixed header)、可变头(Variable header)、消息体(Payload)三部分构成。
注意2点:
1)所有的数据包结构都用16进制来表示,注意是16进制,不是10进制表示报文结构。
2)使用大端序(big-endian,高位字节在低位字节前面)。这意味着一个16位的字在网络上表示为最高有效字节(MSB),后面跟着最低有效字节(LSB),举个例子,比如用一个字节来表示1,那就是00000001,这里面前面的0000是高位,0001是低位,这是小端表示方式,而用大端表示的话,就要将原来的低位变高位,原来的高位变低位,即0001放高位,0000放低位,形成了00010000,也就是指00000001变成了00010000,转换成16进制就是16,说明原来的1变成了16,这就大端与小端的意思。
-
(1)固定头(Fixed header)。存在于所有MQTT数据包中,表示数据包类型及数据包的分级,数据包类型就有连接,订阅,发布,取消订阅,心跳等内容,后面具体讲,所有类型的MQTT协议中,都必须包含固定头,而分级是指服务质量 (QoS)。
-
(2)可变头(Variable header)。存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
-
(3)消息体(Payload)。存在于部分MQTT数据包中,表示客户端收到的真正内容。与可变头一样,在有些协议类型中有消息内容,有些协议类型中没有消息内容。
所以:mqtt 报文结构 = 固定报头 + 可变报头 + 有效载荷
为什么要分析研究这些报文结构,因为要写程序啊?程序当中需要将这些报文一个个组装起来,发给服务器,服务器才会响应处理,协议结构不对,肯定不会有正确结果。
2、固定报头
固定头包含3部分内容,占2个字节,注意是2个字节,那就是16位,即16个二进制的长度。
从图可看出,报文类型和报文类型标志位占1个字节,剩余长度占1个字节,byte就是字节的意思,一个字节占8个bit,从8个0到8个1,即00000000到11111111的范围:
控制报文的类型:用于标示类型,如:连接(CONNECT)报文,发布(PUBLISH)报文等。他占了4个二进制。如:连接报文对应二进制:0001。
控制报文类型的标志位:这里包含的内部比较多。分别为:标示发送重复数(DUP) 、服务质量 (QoS) 、保留标志(RETAIN)。
剩余长度:这是指剩余字节的长度,意思是指从它开始到最后一共有多少个字节,比如1A就是26,表示包括它自己在内共有26个字节,自己占1个字节,那后面实际就是25个字节,注意是字节,不是字符,00表示一个字节,00是两个字符,所以是2个字符表示1个字节,注意这个意思的理解。
控制报文类型,对应第1个字节的7--4的位置,如下所示:
控制报文类型,对应第1个字节的3--0的位置,如下所示(实际上只有少数报文类型有控制位):
可以看到固定报文共占2个字节
3、可变报头
Variable Header的意思是可变化的消息头部。MQTT
数据包中包含一个可变头,它驻位于可变头(Variable header)与消息体(payload)之间。可变报头的内容根据报文类型的不同而不同,也就是指在有些协议类型中存在,在有些协议类型中不存在。
绿色的为用到的。红色表示没有用到的
举个列子,如连接确定(CONNACK)报文,他的可变报头只有连接确认标志和连接返回码。因此得到一个结论:不同控制报文可变头部不同,那么它占几个字节了?它占N个字节,因为它是变化的所以所占字节是变化的。
虽然可变报头是变化的。但是总元素是不会发生变化的。根据MQTT文档说明如下:
1、协议名称长度
注意这个是指协议名称长度,占2个字节,通俗地理解就是指"MQTT"这个字符串的长度,我们知道"MQTT"这个字符串的长度就是4,这个数字"4"要用2个字节来表示,4用2个字节来表示的话就是04,用16进制表示就是0x04,0x表示16进制,这里有点辣条的味道,不好理解。
2、协议名称
协议名称必须是MQTT,这是不能变的,它占4个字节,MQTT的字节分别是71,81,84,84,为什么了?查ascii码看到的
明白了吗?协议名称的长度和协议名称是不同的概念
3、协议级别
可以看出,它占1个字节
mqtt 3.1.1 版协议就是4,这是固定的,用16进制表示就是0x04,0x表示16进制。
4、连接标志
连接标志占1个字节,它包含一些用于指定 MQTT 连接行为的参数。它还指出有效载荷中的字段是否存在。服务端必须验证 CONNECT 控制报文的保留标志位(第 0 位)是否为 0,如果不为 0 必须断开客户端 连接。Reserved 为以保留。
以上这个字节的8个位的含义如下:
0、Reserved
这个位保留
1、CleanSession
第一位CleanSession指定了会话状态的处理方式,控制会话状态生存时间,0代表保留会话,当连接断开后,客户端和服务端必须保存会话信息,QoS 1 和 QoS 2 级别的消息保存为会话状态的一部分,服务端也可以保存满足相同条件的 QoS 0 级别的消息。1代表清除会话,不保留离线消息,重连会建立新的会话。
2、遗嘱标志
遗嘱标志(Will Flag)被设置为 1,表示如果连接请求被接受了,遗嘱(Will Message)消息必须被存储在服务端并且与这个网络连接关联。之后网络连接关闭时,服务端必须发布这个遗嘱消息,除非服务端收到 DISCONNECT 报文时删除了这个遗嘱消息
遗嘱消息发布的条件,包括但不限于: •
服务端检测到了一个 I/O 错误或者网络故障。 •
客户端在保持连接(Keep Alive)的时间内未能通讯。 •
客户端没有先发送 DISCONNECT 报文直接关闭了网络连接。 •
由于协议错误服务端关闭了网络连接。
遗嘱消息连接标志位 WILL QOS 和 WILL RETAIN 字段会被服务端用到,同时有效载荷中必须包含 WILL TOPIC 和WILL MESSAGE 字段,遗嘱标志被设置为 0,连接标志中的 WILL QOS 和 WILL RETAIN 字段必须设置为 0,并且有效载荷中不能 包含 WILL TOPIC 和 WILL MESSAGE 字段
3、4、遗嘱Qos
位置:连接标志的第 4 和第 3 位。
如果遗嘱标志被设置为 0,遗嘱 QoS 也必须设置为 0。
如果遗嘱标志被设置为 1,遗嘱 QoS 的值可以等于 0,1,2。
5、遗嘱保留
如果遗嘱标志(Will Flag)被设置为 0,遗嘱保留(Will Retain)标志也必须设置为 0。
如果遗嘱标志(Will Flag)被设置为 1:
如果遗嘱保留被设置为 0,服务端必须将遗嘱消息当作非保留消息发布。
如果遗嘱保留被设置为 1,服务端必须将遗嘱消息当作保留消息发布
6 、用户名标志
如果用户名(User Name)标志被设置为 0,有效载荷中不能包含用户名字段。
如果用户名(User Name)标志被设置为 1,有效载荷中必须包含用户名字段。
7、密码标志
如果密码(Password)标志被设置为 0,有效载荷中不能包含密码字段。
如果密码(Password)标志被设置为 1,有效载荷中必须包含密码字段。
如果用户名标志被设置为 0,密码标志也必须设置为 0。
5、保持连接
Keep Alive表示保持连接,它占2个字节,意义在于告诉服务器,客户端还存在。指客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔,如果没有任何其它的控制报文可以发送,客户端必须发送一个 PINGREQ 报文,不管保持连接的值是多少,客户端任何时候都可以发送 PINGREQ 报文,客户端收到服务器返回 PINGRESP 报文判断网络和服务端的活动状态 。
6、可变报头示例:
可以看到这个可变报头有10个字节:分别是协议名称长度2+协议名称4+协议级别1+连接标志1+保持连接2=10。
4、有效载荷
可以说是客户端和服务端之后间的通信内容。但不是什么类型的报文都必须有。而且有效载荷部分的总信息又不是只有通信内容,他有可能会出现别的信息。如:主题名(Topic Name)、客户ID(Client Identifier)等信息,那么它占多少个字节,它占N个,即不确定的,可变动的字节数。
绿色的为用到的。红色表示没有用到的
有效载荷有5个部分构成,具体组成如下:
具体含义表示:
1、 客户端标识符
①、服务端使用客户端标识符 (ClientId) 识别客户端。连接服务端的每个客户端都有唯一的客户端标识符(ClientId)。
②、客户端标识符 (ClientId) 必须存在而且必须是 CONNECT 报文有效载荷的第一个字段
③、服务端可以允许客户端提供一个零字节的客户端标识符,这样服务端会认为是特殊情况自动分配一个且唯一,那样必须将同时将清理会话标志设置为 1
2、遗嘱主题
如果可变报头连接标志部分遗嘱标志被设置为 1,则有效载荷的下一个字段是遗嘱主题(Will Topic)。
3、 遗嘱消息
如果可变报头连接标志部分遗嘱标志被设置为 1,有效载荷的下一个字段是遗嘱消息。
4、 用户名
如果可变报头连接标志部分用户名(User Name)标志被设置为 1,有效载荷的下一个字段就是它。
5、 密码
如果可变报头连接标志部分密码(Password)标志被设置为 1,有效载荷的下一个字段就是它。
注意:客户端提供的 ClientId 为零字节且清理会话标志为 0,服务端必须发送返回码为 0x02(表示标识符不合格)的 CONNACK报文响应客户端的 CONNECT 报文,然后关闭网络连接,也就是说如果你不指定 clientId ,必须清除连接(即将 cleansession 设置为 true)
5、小结
了解MQTT报文的格式之后,对于我们后面学习相关的响应动作非常有帮助,希望对大家有帮助,初次看肯定觉得很复杂,那是当然的,没有关系,有困难是暂时的,只要能啃,多看多搞,一定熟练到位,火箭不是推的,牛逼可以吹的。