MQTT——连接报文

时间:2021-07-13 11:32:26

学习MQTT协议。如果只是看了相关文档就认为可以了。那是一个错误的观念。笔者为了能更好的去理解MQTT协议。看了不少相关的开源Broker的项目。可惜这些项目一般都是不完全的。不过从这些项目中笔者至少发现他们大部都是通过Netty这个通信框架来完成的。哪怕是大型项目ActiveMQ也脱不了俗。特别是商用HiveMQ更是列为重要的一部分。所以笔者接下来会用Netty框架来实现一些代码。这样子有助于我们去理解MQTT协议。

本节笔者会来讲连接报文(CONNECT)。可以说他是所有报文的基础。所有的动作都必须在连接之上操作。我们都知道MQTT是基于TCP/IP网络协议的。并以字节流传输的。他的行为动作更为简单。如下

MQTT——连接报文

我们可以知道连接会用到俩个报文类型类——CONNECT报文和CONNACK报文。其中CONNECT报文比较复杂一点。可以说是所有报文中信息种类最多的。CONNACK报文的最大特点就是没有有效载荷部分。

接下笔者就会讲解一下连接的相关行为。同时也希望读者们记住笔者这里讲的一般是MQTT 3.1MQTT 3.1.1的协议。

CONNECT报文就是相当连接请求一样子。所以当客户端和服务端建立之后,服务端接受的第一份报文就必须是连接报文。相信这个不用笔者说明也知道为什么。同时客户端这边要保证连接报文只能发送一次。同时在服务端也要有验证来自客户端的连接报文只有一次。如果发送俩次以上的连接报文的话,不好意思请当作违反了协议断开当前连接。从前面的图片里面我们就知道如果客户端发送一个连接报文(CONNECT)之后,服务端就会返回一个连接确定报文(CONNACK)。如果客户端在一段时间之后,还没有接收到来自服务端的连接确定报文(CONNACK)的话,客户端一定要断开连接。同时要重起一个新的网络连接。在发一次连接报文(CONNECT)。

我们都知道控制报文分为固定报头+可变报头+有效载荷部分。其中可变报头和有效载荷并不是必须要拥有的。而CONNECT报文却是所有中笔者认为最为复杂的报文。不管是可变报头还是有效载荷部分他都拥有。

固定报头

固定报头的结构上一单已讲过了。占八个字节。从上一单的报文类型列表我们知道他的值为1。报文类型占四个字节。所以他的二进制就是0001。在MQTT 3.1里面有讲到DUP、QoS、 RETAIN 都没有被用到。这些加起来占四个字节。这样子的话笔者以为固定报头最终的二进制是0001。但是在MQTT 3.1.1里面却又说到以0保留了。所以最终二进制是00010000。简单的讲DUP、QoS、 RETAIN虽然没有被用到,所以设置为0。

可变报头

连接报文(CONNECT)的可变报头的结果构分为四个部分:协议名(Protocol Name)、协议等级(Protocol Level)、连接标志(Connect Flags)、保持连接(Keep Alive)。其实协议名(Protocol Name)和协议等级(Protocol Level)笔者也不是很明白有什么用。只是知道MQTT 3.1 的协议名是MQIsdp,协议等级是3。而MQTT 3.1.1的协议名却是MQTT,等级是4。好吧。至少说明不同版本的MQTT协议存在不同的协议名和协议等级。这里面在文档也有一个动作存在。就是如果服务端发现协议名不正确的话,就必须断开连接。如果是协议等级不对话,就是必须回返一个码。

要问可变报头里面最重要的部分是哪一部分的话,笔者认为是连接标志(Connect Flags)。连接标志里面包括用户名标志(User Name Flag)、密码标志(Password Flag)、遗嘱保留(Will Retain)、清理会话(Clean Session)、保留(Reserved)等。他们的位置如图下。

MQTT——连接报文

  清理会话(CleanSession)

客户端和服务端之间的通信时间,笔者认为是一次会话。也就是在连接成功的时候为会话开始。当断开连接的时候表示一次会话结束了。即然是会话,自然就有会话状态。这些状态当前就是指通信之间的信息。如下

一、服务端的会话状态:

1)客户端的订阅信息。

2)已经发送给客户端,但是还没有完成确认的QoS 1和QoS 2级别的消息。

3)即将传输给客户端的QoS 1和QoS 2级别的消息

4)已从客户端接收,但是还没有完成确认的QoS 2级别的消息

5)准备发送给客户端的QoS 0级别的消息(可选)

二、客户端的会话状态:

1)已经发送给服务端,但是还没有完成确认的QoS 1和QoS 2级别的消息

2)已从服务端接收,但是还没有完成确认的QoS 2级别的消息

知道了会话状态,就可以明白清理会话就用来表示是否清除或是保存会话状态。如果清理会话为0的话,表示要保存这里会话状态。如果连接断开了,在次连接的时候,服务要以什么来获得已存在的会话状态呢?客户ID。这是在有效载荷分部分的下面会讲到。根据客户ID来找查有没有相关的会话状态存在。如果存在,就必须以存在的会话状态来建立连接。如果没有就创建一个新会话状态。当然断开之后,不管是客户端还是服务端都要保存当前会话状态。这个举动笔者认为是为了数据重用吧。也可以说是保证数据不会丢失。如果清理会话为1的话,事情就变的很简单。给我清除掉就是行了。没有一次连接都是一个新的会话。

  遗嘱标志(Will Flag)

关看到遗嘱俩个字就应该明白。用于表示在网络连接突关闭的时候,要不要发送遗嘱。遗嘱标志可关系遗嘱QoS(Will QoS),遗嘱保留(Will Retain),还有有效载荷里面的遗嘱主题(Will Topic)和遗嘱消息(Will Message)。可以说遗嘱标志是遗嘱功能总开关。只当遗嘱标志为1的时候。说明要用到遗嘱功能。那遗嘱QoS(Will QoS),遗嘱保留(Will Retain)就可以被启用。也就是说遗嘱主题(Will Topic)和遗嘱消息(Will Message)必须要在有效载荷部分里面出现。

  遗嘱QoS(Will QoS)

他的值主要要看遗嘱标志(Will Flag)是的值。如果遗嘱标志(Will Flag)是1的话,遗嘱QoS可以是0,1,2。这些值表示跟服务质量是一样子。如果遗嘱标志(Will Flag)是0的话,那么遗嘱QoS必须也是0;

  遗嘱保留(Will Retain)

他的值也是要看遗嘱标志(Will Flag)是的值。如果遗嘱标志(Will Flag)是1的话,遗嘱保留可以是0或1。用于表示有没有做于保留消息发布;

用户名标志 User Name Flag 和 密码标志 Password Flag

笔者为什么把这俩个放在一起呢?相信做过很多业务开发的人都知道用户名和密码。上面这俩个就是用于标示在有效载荷里有没有相关的部分。比如。当就用户名标示为1的时候,那就是说明有效载荷部分里面有用户名的信息。同理密码标示为1 就是表示有效载荷部分有密码的信息。这里有一点要注意就是如果用户名标志为0的话,那密码标志就必须为0。有用户名和密码的话,我们就可以在服务端做一些身分验证的业务。同时还可以加入一些权限。

上面笔者讲过可变报头分为四个部分。剩下一个保持连接(Keep Alive)。保持连接(Keep Alive)表示在客户端一个报文发送结束之后到下一次报文发送之前的空闲时间。单位为秒。记住是在客户端而不是服务端。如果在保持连接的时间内没有发送任何报文的话。文档里面是要求发送一个PINGREQ报文。通过他判断服务端和客户端之间的连接状态。如果在一时间段接受不到没有收到服务端发来PINGRESP报文,那么就应该关闭于服务端的连接。前面讲都是在客户端这边要做的事情。服务端这当然也不能少。如果服务端判断保持连接不为0的时候,就要以保持连接值的1.5陪时间来判断是否有接到报文。如果没有接受报文的话,就要关闭跟客户端之间的连接。值得注意的事。服务端要断开客户端不是根据保持连接来处理。而是查看这个客户端是不是闲了。如果是的话,什么时候都可以断开连接的。

有效载荷

有效载荷事实上就是通信里面的用户要存放的信息。只是这里面又不能全是用户信息。还包括了一些MQTT需要的信息。这跟可变报头的一些标志有关系。但是不管什么样子。客户ID是必须在第一位的。后面就是遗嘱主题和遗嘱消息。最后才是用户和密码。

有效载荷 = 客户ID + 遗嘱主题 + 遗嘱消息 + 用户 + 密码

为了方便学习,笔者用软件把包搞下来。上一章也讲到过什么样子搞下。如下图下

MQTT——连接报文

上面的图片算是比较全的连接报文。笔者也根据相应的标示出他们所在的位置。图片下方是报文相应的二进制流。红线标出的数据是报文真时的数据对应。而绿色线表示是接下来红线相关数据的长度。相信如果你看过MQTT相关的文档就应该知道MSB和LSB关键字的作用。那么图片1红线就是固定报头,2红线就是可变报头。从3红线开始后面都是有效载荷部分。

通上面我们大至能了解MQTT的连接过程要做些什么。在MQTT文档里面有一点笔者有一点吃惊。笔者以为如果一个客户端发送连接报文(CONNECT)之后,并接受到了服务端的连接报文确定(CONNACK)之后,才可以有进行发布相关信息。可是MQTT文档指出客户端在发送连接报文(CONNECT)之后就可以进行发布了。而不需要等待连接报文确定(CONNACK)。如果服务端因为客户端不合适,可以完全不需要去处理和反应之前送来的消息。

连接报文确定(CONNACK)的特点就是没有有效载荷的部分。客户端想要知道自己有没有连接成功服务端。就必须去查看可变报头里面的回返码。在CONNACK报文的可变报头里面还有一叫Session Present。他用于表示服务端没有保存会话状态。这个笔者就不多讲。各位自己看下图吧。

MQTT——连接报文

注意:红线为Session Present,绿线为返回码。