前言

在前面的文章中,我们对MQTT的应用场景,MQTT服务器搭建、MQTT client工具测试进行了简单的描述,对MQTT有了初步的了解,从本文开始,将会详细的分析MQTT协议内容。

1.MQTT 控制报文结构

名称

备注

Fixed header固定头

所有的控制报文都包含

Variable header可变报头

部分控制报文包含

Payload有效载荷

部分控制报文包含

2 固定头

每个MQTT控制报文都包含了一个固定头,下图描述了固定头的格式

java mqtt解析收到的报文 mqtt协议报文_服务端

2.1 MQTT控制报文类型

位置:第1个字节,第4~7bit,共4位无符号值,这些值的定义如下表:

名字


报文流动方向

描述

Reserved

0

禁止

保留

CONNECT

1

客户端到服务端

客户端请求连接服务端

CONNACK

2

服务端到客户端

连接报文确认

PUBLISH

3

双向

发布消息

PUBACK

4

双向

QoS 1消息发布收到确认

PUBREC

5

双向

发布收到(保证交付第一步)

PUBREL

6

双向

发布释放(保证交付第二步)

PUBCOMP

7

双向

QoS 2消息发布完成

SUBSCRIBE

8

客户端到服务端

客户端订阅请求

SUBACK

9

服务端到客户端

订阅请求报文确认

UNSUBSCRIBE

10

客户端到服务端

客户端取消订阅请求

UNSUBACK

11

服务端到客户端

取消订阅报文确认

PINGREQ

12

客户端到服务端

心跳请求

PINGRESP

13

服务端到客户端

心跳响应

DISCONNECT

14

客户端到服务端

客户端断开连接

Reserved

15

禁止

保留

2.2 控制报文标志

固定报头的第1个字节的剩余4位[3-0]包含了每个MQTT控制报文类型特定的标志,如果收到非法的标志,接收者必须关闭网络连接,标志如下,其中任何标记为“Reserved”的标志位,都是保留给以后使用的。

java mqtt解析收到的报文 mqtt协议报文_java mqtt解析收到的报文_02


其中:

DUP = 控制报文的重复分发标志

QoS = PUBLISH报文的服务质量等级

RETAIN = PUBLISH报文的保留标志

2.3 剩余长度

位置:从第2个字节开始
剩余长度表示当前报文剩余部分的字节数,包括可变报头负载数据。剩余长度不包括用于编码剩余长度字段本身的字节数。

剩余长度字段使用了一个变长度编码方案,对于小于128的值,它使用单字节编码,更大的值则按下面的方式处理:
低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码128个(0-127/0x00-0x7F)数值和一个延续位(continuation bit),剩余长度的字段最大4个字节。

举例1:十进制数64,由于小于127,所以会被编码为1个字节,对应的十六进制为 0x40
举例2:十进制321(=65+2128),所以会被编码为2个字节,最低有效位在前
第1个字节为 65+128 =193,注意这里的128是表示其最高位为1,表示后面还有字节。
第2个字节为2,表示 2
128 = 256,
所以本例中的剩余长度编码为 0xC1,0x02,我们去计算的时候是这样:

len = 0xC1&0X7F + 0x02*0x7F // 321

下表中描述了不同字节数表示的剩余长度大小

字节数

最小值

最大值

1

0(0x00)

127(0x7F)

2

128(0x80, 0x01)

16383(0xFF,0x7F)

3

16384(0x80, 0x80, 0x01)

2097151(0xFF, 0xFF, 0x7F)

4

2097152(0x80, 0x80, 0x80, 0x01)

268435455(0xFF, 0xFF, 0xFF, 0x7F)

所以MQTT协议 理论上允许发送最大 256MB(268435455)大小的控制报文。

3. 可变报头

某些MQTT控制报文包含了一个可变报头部分,它在固定报头和负载之间,可变报头的内容根据报文类型的不同而不同,可变报头的报文标识符(Packet Identifier)字段存在与多个类型的报文里。
可变报头其实就是MQTT开发中使用的 Packet ID,通过Packet ID 进行一些操作确认。

3.1 报文标识符

报文标识符类型

java mqtt解析收到的报文 mqtt协议报文_服务端_03


很多控制报文的可变报头部分包含了两字节的报文标识符字段,这些报文是PUBLISH(QoS>0时),PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCIBE, UNSUBACK.

SUBSCRIBE, UNSUBSCRIBE和PUBLISH(QoS > 0)控制报文必须包含一个非零的16位报文标识符(Packet ID)。客户端每次发送一个新的这些类型报文时,都必须分配一个当前未使用的报文标识符(我们在开发的时候可以做简单的 自增packet_id++)。如果一个客户端要重发这个特殊的控制报文,爱随后重发的那个报文时,它必须使用相同的标识符。当客户端处理完这个报文对应的确认后,这个报文标识符就可以释放了,也就是可以重新使用。

Qos1的PUBLISH 对应的是PUBACK, Qos2的PUBLISH 对应的是PUBCOMP, 与SUBCRIBE或UNSUBSCRIBE对应的分别是SUBACK或UNSUBACK,服务器返回的Packet ID与客户端发送的是一致的,通过这一机制来实现确认。

Qos设置为0的PUBLISH报文不能包含报文标识符。

PUBACK, PUBREC, PUBREL报文必须包含于最初发送的PUBLISH报文相同的报文描述符,同样的,SUBACK和UNSUBACK必须包含在对应的SUBSCRIBE和UNSUBSCRIBE报文中使用的标识符。

需要包含报文标识符的控制报文

控制报文

报文标识符字段

CONNECT

不需要

CONNACK

不需要

PUBLISH

需要(如果Qos > 0)

PUBACK

需要

PUBREC

需要

PUBREL

需要

PUBCOMP

需要

SUBSCRIBE

需要

SUBACK

需要

UNSUBSCRIBE

需要

UNSUBACK

需要

PINGREQ

不需要

PINGRESP

不需要

DISCONNECT

不需要

客户端和服务端彼此独立低分配报文标识符,因此客户端、服务端组合使用相同的报文标识符,可以实现并发的消息交换。

4.有效载荷

某些MQTT控制报文在报文的最后部分包含了一个有效载荷,比如对于PUBLISH来说,有效载荷就是应用消息。下表中列出了需要有效载荷的控制报文类型

控制报文

有效载荷

CONNECT

需要

CONNACK

不需要

PUBLISH

可选

PUBACK

不需要

PUBREC

不需要

PUBREL

不需要

PUBCOMP

不需要

SUBSCRIBE

需要

SUBACK

需要

UNSUBSCRIBE

需要

UNSUBACK

不需要

PINGREQ

不需要

PINGRESP

不需要

DISCONNECT

不需要

5 小结

MQTT协议报文的结构由3部分组成,可以简单的理解为:
(1)固定报头:表示这个报文的类型(CONNECT\PUBLISH…)
(2)可变报头:Packet ID,也就是 报文的 顺序ID。
(3)有效载荷:应用消息。