MQTT协议

之前弄的MQTT协议,主要做的是QoS0和QoS1的发布者(也做了subscibe),移植了paho-mqtt中的代码,下面是具体的部分协议解析以及抓到的LOG分析。

mosquitto

       mosquitto是一款开源的mqtt服务端软件,可以部署在服务器上用于接收、下发消息,在这里我用它来调试终端的MQTT协议(需要注意mosquitto使用的mqtt协议版本为V3.1和目前最新的V3.1.1也就是第四版协议有些许出入,这在调试客户端协议时需要注意)。

在Windows下安装mosquitto:
在windows下搭建MQTT服务器,使用Cygwin64+mosquitto64(Cygwin64可以不用安装)

在Linux下安装mosquitto:
wget http://mosquitto.org/files/source/mosquitto-1.3.4.tar.gz//下载源代码包
tar zxfv mosquitto-1.3.4.tar.gz
cd mosquitto-1.3.4
make
sudo make install

安装注意点:
【1】编译找不到openssl/ssl.h
【解决方法】——安装openssl
sudo apt-get install libssl-dev
【2】编译过程找不到ares.h
【解决方法】——修改config.mk中的WITH_SRV:=yes,改为WITH_SRV:=yes
【3】使用过程中找不到libmosquitto.so.1
error while loading shared libraries: libmosquitto.so.1: cannot open shared object file: No such file or directory
【解决方法】——修改libmosquitto.so位置:
创建链接
sudo ln -s /usr/local/lib/libmosquitto.so.1 /usr/lib/libmosquitto.so.1
更新动态链接库
sudo ldconfig
【4】make: g++:命令未找到【解决方法】
安装g++编译器:sudo apt-get install g++

建立MQTT连接

       建立MQTT连接主要有两部分:1、发送CONNECT报文。2、收到确认连接的CONNACK报文。以下分别作出解析,并在最后附上LOG分析。

CONNECT报文:

在MQTT中CONNECT报文由固定报头、可变报头、有效载荷三部分组成。
如下图所示,为CONNECT连接报文的固定报头:

由图中可以看出此固定报头只有两个字节,第一个字节为固定的0x10,第二个字节为此条报文的剩余长度。
如下图所示,为CONNECT连接报文中的可变报头:

从图中可以看出该可变报头由10个字节组成,前6个字节为固定值,分别是协议名字符数的最高有效位、协议名字符数的最低有效位以及协议名字。第7个字节为协议的版本,此处由于后期测试开源软件所使用的MQTT协议版本为第三版所以这里需要将其改为3。接下来第8个字节中包含了CONNECT报文的诸多信息,下面分别作出解释:
       首先是第一位CleanSession(清理会话),该标志位用于控制会话状态的生存时间。如果该标志被设置为0,则服务端必须基于当前会话的状态恢复与终端的通讯。当连接断开之后,终端和服务端必须保存会话消息。如果该标志被置为1,若网络断开,终端和服务端必须丢弃之前的任何会话,待连接上后开始一个新的会话。也就是说会话仅持续和网络连接同样长的时间。与这个会话关联的状态数据不能被任何之后的会话重用。由于此终端并不需要保存之前会话,所以将该位置为1。
       第二位Will Flag(遗嘱标志),该标志被置1,则表示如果连接请求被接受了那么有效载荷中的遗嘱(Will Message)消息必须被存储在服务端,并且与这个网络连接关联,若网络连接被服务器关闭时,服务端必须发布这个遗嘱消息,若终端发送DISCONNECT报文请求主动断开连接,则服务端需要删除这个遗嘱消息,不进行发布。由于本设计对数据的实时性要求较高,所以并不需要使用遗嘱消息机制,所以将该位置0。
       第三、四位为遗嘱的QoS等级标志,如果遗嘱标志被置为0,那么这两位也必须置0。若遗嘱标志被设置为1,则遗嘱的QoS值可设置为0、1、2。在设计中由于并不使用遗嘱所以这两位被写为0。
       第五位为遗嘱保留位,若遗嘱标志为0,则此位也需要被设置为0。若遗嘱标志位为1,此位为0则遗嘱消息作为非保留消息发布,若此位为1则遗嘱消息需要作为保留消息发布。由于并未使用遗嘱,所以该位被置为0。
       第六位为密码标志,若此位为0,则之后的有效载荷中不能包含密码字段。若此位为1,则之后的有效载荷中必须包含密码字段。
       第七位是用户名标志,当此标志被置为1时,有效载荷中必须包含用户名字段,若为0则有效载荷中不能含用户名字段。在本次设计中还未设计到数据安全性问题,所以暂不使用用户名和密码服务。
       对于CONNECT连接可变报头的最后两个字节即第9和第10个字节,是作为配置连接保持的时间来使用,第九字节为保持连接的最高有效位,第十字节为保持连接的最低有效位,它指的是在终端传输完成一个控制报文到发送下一个报文时,二者之间允许的最大空闲时间间隔。终端负责保证控制报文发送的时间间隔不超过保持连接的值。如果没有任何其他的控制报文发送,终端必须发送一个心跳报文。如果保持连接值非0,并且服务端在1.5倍的保持连接时间内没有收到终端的控制报文,服务端就必须断开和终端的网络连接。若保持连接值为0,则表示关闭保持连接功能,此时服务端不需要因为终端不活跃而断开连接。此处由于GPRS模块可以在无数据交互的情况下保持12分钟的网络连接,所以将这两个字节写为0x02和0x58(10分钟)。
       下面说明CONNECT可变报文中的有效载荷。从以上分析、说明中可知可变报头中的标志决定了是否包含这些字段。如果包含的话必须按照以下顺序出现:终端标识符,遗嘱主题,遗嘱消息,用户名和密码。其中终端标识符必须存在,且必须是CONNECT报文有效载荷的第一个字段,需要被定义为UTF-8编码的字符串。它是作为和服务端连接的终端ID,每个终端都唯一的客户标识符。由于之前可变报头中并没有设置遗嘱、用户名及密码,所以目前有效载荷内就只有定义的终端标识符。

CONNACK报文:

       当终端按照上述格式发送了CONNECT报文后,服务端必须回复一个CONNACK报文作为确认连接请求。CONNACK报文格式如图2.9所示。
       从图中可以看出,CONNACK报文由固定报头和可变报头组成,其中固定报头为两字节的固定值0x20,0x02。可变报头则包含了连接确认标志和连接返回码,其中,连接确认标志的1至7位都必须设置为0,第0位为当前会话标志,它的作用主要是用于检测服务端和终端是否在已存储的会话状态上保持一致。具体返回码报文格式及其含义如下图所示:


       从上图可以看出只有当连接返回码为0x00时,连接才被正确建立。至此,完成了MQTT协议中建立连接部分的内容分析。

QoS0消息:

       接下来分析MQTT的PUBLISH报文,终端通过发送PUBLISH报文向服务器发送消息,若此消息为QoS0则不要求服务端做出响应,若此消息的服务质量等级大于0则需要服务端做出相应响应。
       从MQTT的文档中可以知道PUBLISH报文也是由固定报头、可变报头和有效载荷三部分组成。PUBLISH报文的固定报头格式如下图所示:

       从图中可知在PUBLISH固定报头中,第1个字节的第0位为消息保留标志,若此标志被置1,则服务端必须存储该条消息以及它的QoS,当有一个订阅者订阅同主题的消息时,服务端需要将这条消息发送给那个订阅者。第1和第2位可设置此消息的质量服务等级。第3位DUP为重发标志,该位置0,表示这是终端第一次发送这个PUBLISH报文,若该位置1,则说明这条消息是一条重发消息,若要发送QoS0的消息,则该位必须为0。
       下图为PUBLISH报文的可变报头格式:

       该可变报头主要分为两部分,即主题名和报文标识符。第1和第2个字节为主题名所占字符数的最高有效位和最低有效位。之后的一个字段便是主题名,主题名是用于识别PUBLISH报文有效载荷即消息,所对应的信息通道的。在主题名之后的两个字节分别作为报文标识符的最高和最低有效位,它是用于在QoS1和QoS2的消息传递中区分各条消息,也就是消息ID,若需要重发该PUBLISH消息,则需要使用相同的报文标识符,并且需要将DUP置一。当终端处理完这个消息所对应的响应后,该报文标识符可重用。
       紧跟在可变报文之后的是PUBLISH报文的有效载荷,即该主题名所对应的消息,所以QoS0的消息只需要发布一条PUBLISH报文便可以将消息上传到平台,大大节约了网络开销,降低了功耗。

QOS1消息:

       对于终端所发出的QoS1报文,若通讯链路正常情况下服务端还将发送一个PUBACK报文,作为终端PUBLISH报文的一个响应(PUBACK报文格式如下图所示):

       从图中可知,PUBACK报文没有有效载荷,它和CONNACK相似,都由固定报头和可变报头组成,且固定报头都为一定值。唯一不同的便是PUBACK报文的可变报头变为报文标识符的最高有效位和最低有效位,此报文标识符对应着终端发送的PUBLISH报文中的报文标识符。即终端采用QoS1质量等级发送PUBLISH后,服务端将此PUBLISH报文中的报文标识符取出,加入到PUBACK报文中发出,若终端收到了带相同标识符的PUBACK报文,则代表一次QoS1通讯成功,终端可以在下一个PUBLISH报文里继续使用该标识符。若终端没能收到相对应的PUBACK报文,则说明消息未能成功发送。可能需要重新发送该条消息。
下面是相应的通讯Sequence以及部分log分析:
Sequence:

部分LOG分析: