在MQTT协议中,一个控制报文(数据包)的结构按照前后顺序分如下三部分:
Fixed header |
固定报头 |
报文的最开始部分,所有报文都包含这个部分 |
Variable header |
可变报头 |
固定报文的附加部分,有些报文没有这个部分 |
Payload |
有效载荷 |
需要携带的信息内容,有些报文没有这个部分 |
下图是MQTT控制报文(数据包)格式的结构示意图:

固定报头存在于所有MQTT数据包中,表示数据包类型及控制类标志等。固定报头由至少2个字节组成,格式如下:
Bit(位号) | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Byte1(第一个字节) |
组合代表MQTT控制报文(数据包)的类型 |
控制报文的标志位(Flags),可理解为一种属性参数 |
Byte2(第二个字节起) |
剩余长度,当前报文剩余部分的字节数,包括可变报头和有效负载 |
1.1、控制报文类型(Control Packet type):
固定报头第一个字节的高四位(7-4号位)是代表控制报文的类型,也就是这个数据包是做什么用的。是用7-4号位的二进制(也就是1111--0000之间)组合值,来代表具体的含义,见下表:
0000 |
0 |
Reserved |
禁止 |
保留,不可用 |
0001 |
1 |
CONNECT |
客户端―→服务端 |
客户端请求连接到服务端的代理服务 |
0010 |
2 |
CONNACK |
客户端←―服务端 |
连接请求的回复确认报文 |
0011 |
3 |
PUBLISH |
客户端←→服务端 |
发布主题消息 |
0100 |
4 |
PUBACK |
客户端←→服务端 |
发布确认,是QoS=1时,对 PUBLISH 的响应确认 |
0101 |
5 |
PUBREC |
客户端←→服务端 |
发布收到,是QoS=2时,对 PUBLISH 的响应确认,是QoS=2实现的第一步 |
0110 |
6 |
PUBREL |
客户端←→服务端 |
发布释放,是QoS=2时,对 PUBREC 的响应确认,是QoS=2实现的第二步 |
0111 |
7 |
PUBCOMP |
客户端←→服务端 |
发布完成,是QoS=2时,对 PUBREL 的响应确认,是QoS=2实现的第三步 |
1000 |
8 |
SUBSCRIBE |
客户端―→服务端 |
客户端订阅主题,可一次订阅一个或多个主题(使用通配符) |
1001 |
9 |
SUBACK |
客户端←―服务端 |
订阅完成确认,是对 SUBSCRIBE 的响应确认 |
1010 |
10 |
UNSUBSCRIBE |
客户端―→服务端 |
取消订阅,客户端发起的取消对某个主题的订阅 |
1011 |
11 |
UNSUBACK |
客户端←―服务端 |
取消订阅确认,是对 UNSUBSCRIBE 的响应确认 |
1100 |
12 |
PINGREQ |
客户端―→服务端 |
心跳,表示这个数据包是为通知服务端客户端还在正常连接着 |
1101 |
13 |
PINGRESP |
客户端←―服务端 |
心跳响应,表示服务端已经成功收到了客户端的心跳 |
1110 |
14 |
DISCONNECT |
客户端―→服务端 |
断开连接,客户端通知服务端,需要断开当前网络连接 |
1111 |
15 |
Reserved |
禁止 |
保留,不可用 |
1.2、标志(Flags):
固定报头第1个字节的低4位 (3-0号位)包含每个MQTT控制报文类型特定的标志,必须与控制报文类型配套对应使用,否则服务端代理服务会拒绝服务或断开连接。具体的见下表(保留的标志必须按照表中的值设置):
CONNECT |
保留 |
0 |
0 |
0 |
0 |
CONNACK |
保留 |
0 |
0 |
0 |
0 |
PUBLISH |
使用 |
是否为重复发 |
服务质量高位 |
服务质量低位 |
是否保存消息 |
PUBACK |
保留 |
0 |
0 |
0 |
0 |
PUBREC |
保留 |
0 |
0 |
0 |
0 |
PUBREL |
保留 |
0 |
0 |
1 |
0 |
PUBCOMP |
保留 |
0 |
0 |
0 |
0 |
SUBSCRIBE |
保留 |
0 |
0 |
1 |
0 |
SUBACK |
保留 |
0 |
0 |
0 |
0 |
UNSUBSCRIBE |
保留 |
0 |
0 |
1 |
0 |
UNSUBACK |
保留 |
0 |
0 |
0 |
0 |
PINGREQ |
保留 |
0 |
0 |
0 |
0 |
PINGRESP |
保留 |
0 |
0 |
0 |
0 |
DISCONNECT |
保留 |
0 |
0 |
0 |
0 |
注:关于用“是否”描述的实际就是布尔类型,0表示否,1表示是;
1.3、第一字节各类型报文具体值:
固定报头报文类型高4位和标志位的低4位综合起来,最终第一个字节是有一个具体值的。为了更好的理解第一个字节的具体值是怎样得出来的,在下表列出了不同类型的报文及某个报文不同标志时的具体值:
CONNECT |
连接服务端 |
00010000 |
16 |
0x10 |
CONNACK |
连接成功确认 |
00100000 |
32 |
0x20 |
PUBLISH |
新发布等级0不保存 |
00110000 |
48 |
0x30 |
PUBLISH |
新发布等级0需保存 |
00110001 |
49 |
0x31 |
PUBLISH |
新发布等级1不保存 |
00110010 |
50 |
0x32 |
PUBLISH |
新发布等级1需保存 |
00110011 |
51 |
0x33 |
PUBLISH |
新发布等级2不保存 |
00110100 |
52 |
0x34 |
PUBLISH |
新发布等级2需保存 |
00110001 |
53 |
0x35 |
PUBLISH |
重发等级2不保存 |
00111000 |
56 |
0x38 |
PUBLISH |
重发等级2需保存 |
00111001 |
57 |
0x39 |
PUBACK |
等级1发布成功 |
01000000 |
64 |
0x40 |
PUBREC |
等级2发布收到 |
01010000 |
80 |
0x50 |
PUBREL |
等级2发布释放 |
01100010 |
98 |
0x62 |
PUBCOMP |
等级2发布完成 |
01110000 |
112 |
0x70 |
SUBSCRIBE |
订阅主题 |
10000010 |
130 |
0x82 |
SUBACK |
订阅完成确认 |
10010000 |
144 |
0x90 |
UNSUBSCRIBE |
取消订阅 |
10100010 |
162 |
0xA2 |
UNSUBACK |
取消完成确认 |
10110000 |
176 |
0xB0 |
PINGREQ |
心跳包 |
11000000 |
192 |
0xC0 |
PINGRESP |
心跳回复 |
11010000 |
208 |
0xD0 |
DISCONNECT |
断开网络连接 |
11100000 |
224 |
0xE0 |
注:关于发布主题还有其他情况这里就没有全部列出,根据表中的规律就可以计算出实际的值了。
1.3、剩余长度(Remaining Length):
剩余长度是从第二个字节开始,最多允许占用四个字节。描述本次传送的应用消息在剩余长度字节之后(不包括剩余长度字节本身)还有多少个字节,包括可变报头(有的报文没有这部分) + 有效载荷(有的报文没有这部分)的所有字节数量。
根据上面描述,剩余长度属于变长的编码规则,也就是它可能是1-4个字节中的任何一种情况,那么怎样知道当前这个报文的剩余长度是占用了几个字节的呢?如果不能确定,那么接收方就无法正确解析数据了。所以MQTT协议规定剩余长度的每个字节的最高位(也就是7号位)作为是否还有下一个字节剩余长度的标志位,不做长度数值的表述位。这样每给剩余长度字节最大代表长度值就是127(二进制 1111111 的值)了,因为只有7个位表示长度了。向后每增加一个字节都代表前一个字节满值再加1的倍数,四个字节的剩余长度代表的长度值最大可为268435455。
如果剩余长度值不大于127,则只用一个字节表示,例如121,则剩余长度字节的二进制是01111001,含义见下表:
0 |
1111001 |
接下来没有剩余长度字节了 |
剩余长度是:121 |
如果剩余长度值大于127小于16384,则需用两个字节表示,例如15971,则剩余长度两字节具体值则是0xE3 0x7C(11100011 01111100),含义见下表:
1 |
1100011 |
0 |
1111100 |
后面还有字节描述长度 |
本子节描述长度:99 |
后面没有长度字节了 |
本字节描述长度:124 * 128 = 15872 |
两个字节代表的长度值相加 99 + 15872 = 15971,这既是完整的剩余长度值了。后面这个字节每增加1,则代表剩余长度值增加128。也就是前面字节的低7位值满都为1(127)再加1,就到后面字节加1,前面字节低7位归0。再加满再到后面字节加1,以此类推。所以两个字节可以表述的最大值是(11111111 01111111)127+(127*128) = 16383。
??由于使用了两个字节表述剩余长度,那么前面的字节的最高位7号位就要置1,以告诉解析程序后面的字节还要按照剩余长度来计算。
如果剩余长度值大于16383小于2097152,则需用三个字节表示,例如2097150,则剩余长度三字节具体值则是0xFE 0xFF 0x7F(11111110 11111111 01111111),含义见下表:
1 |
1111110 |
1 |
1111111 |
0 |
1111111 |
还有长度字节 |
长度:126 |
还有长度字节 |
长度:127 * 128 = 16256 |
长度最后字节 |
长度:127 * 16384 = 2080768 |
三个字节代表的长度值相加 126 + 16256 + 2080768 = 2097150,这既是完整的剩余长度值了。3字节每增加1,则代表剩余长度值增加16384,即前两个字节满值再加1。四字节的原理也是这样向后推导,这里就不再列举了。
剩余长度使用1-4个字节可以描述的长度范围见下表:
1 |
0(0x00) |
00000000 |
|
127(0x7F) |
01111111 |
2 |
128(0x80,0x01) |
10000000 00000001 |
|
16383(0xFF,0x7F) |
11111111 01111111 |
3 |
16384(0x80,0x80,0x01) |
10000000 10000000 00000001 |
|
2097151(0xFF,0xFF,0x7F) |
11111111 11111111 01111111 |
4 |
2097152(0x80,0x80,0x80,0x01) |
10000000 10000000 10000000 00000001 |
|
268435455(0xFF,0xFF,0xFF,0x7F) |
11111111 11111111 11111111 01111111 |
可变报头在固定报头与有效负载之间,不是所有的报文都有可变报头。报文类型不同可变报头的内容也不同。后面会对各报文的可变报头逐一讨论。某些类型的报文中的可变报头还包含报文标识符(Packet Identifier)字段。
2.1、报文标识符(Packet Identifier):
报文标识符,一定程度上相当于是每个报文的唯一ID,用于识别报文身份的。重复发送报文时,必须使用相同的报文标识符。在需要应答的控制报文里,标识符可以区分是应答的哪个报文。某些控制报文的可变报头部分包含一个两字节的报文标识符字段。这些报文分别是PUBLISH(QoS > 0时), PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE, SUBACK,UNSUBSCRIBE,UNSUBACK。
??需要使用标识符的报文,发送方在每次发送一个新的报文时,必须分配一个没有使用过的报文标识符。报文标识符固定使用两个字节,按照双字节读值可用范围是0-65535(00000000 00000000 -- 11111111 11111111)。
3、有效载荷(Payload):
在一些需要携带用户自定义的应用消息的MQTT控制报文中,会将这些信息放在报文的最后部分,称之为有效载荷。对于PUBLISH来说有效载荷就是应用消息。不同的控制报文有效载荷内容不同,后面会在分别介绍控制报文时具体讨论。下表列出哪些控制报文有包含有效载荷:
CONNECT |
需要 |
CONNACK |
不 需要 |
PUBLISH |
可选,可以零长度 |
PUBACK |
不需要 |
PUBREC |
不需要 |
PUBREL |
不需要 |
PUBCOMP |
不需要 |
SUBSCRIBE |
需要 |
SUBACK |
需要 |
UNSUBSCRIBE |
需要 |
UNSUBACK |
不需要 |
PINGREQ |
不需要 |
PINGRESP |
不需要 |
DISCONNECT |
不需要 |
本节完,待续......