C#MQTT编程02--报文格式

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报文的格式之后,对于我们后面学习相关的响应动作非常有帮助,希望对大家有帮助,初次看肯定觉得很复杂,那是当然的,没有关系,有困难是暂时的,只要能啃,多看多搞,一定熟练到位,火箭不是推的,牛逼可以吹的。

相关推荐
向宇it4 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
九鼎科技-Leo4 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
Heaphaestus,RC5 小时前
【Unity3D】获取 GameObject 的完整层级结构
unity·c#
baivfhpwxf20235 小时前
C# 5000 转16进制 字节(激光器串口通讯生成指定格式命令)
开发语言·c#
直裾5 小时前
Scala全文单词统计
开发语言·c#·scala
ZwaterZ7 小时前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
ZwaterZ9 小时前
el-table-column自动生成序号&&在序号前插入图标
前端·javascript·c#·vue
SRC_BLUE_1712 小时前
SQLI LABS | Less-55 GET-Challenge-Union-14 Queries Allowed-Variation 2
oracle·c#·less
yngsqq12 小时前
037集——JoinEntities连接多段线polyline和圆弧arc(CAD—C#二次开发入门)
开发语言·c#·swift
Zԅ(¯ㅂ¯ԅ)12 小时前
C#桌面应用制作计算器进阶版01
开发语言·c#