mqtt 协议报文解析
- 一、MQTT报文格式
- 二、MQTT固定报文头[Fixed header]
- 三、mqtt可变报文头
- 四、有效载荷(Payload)
- 五、不同控制报文类型的协议格式
一、MQTT报文格式
MQTT的报文字段主要包含3部分,如下表:
名称 | 说明 |
---|---|
Fixed header(固定报文头) | 所有MQTT报文都包含 |
Variable header(可变报文头) | 只有部分MQTT报文包含 |
Payload(MQTT数据段) | 只有部分MQTT报文包含 |
二、MQTT固定报文头[Fixed header]
每个MQTT报文都包含一个固定报文头,固定报文头部格式如下:
bit 7 6 5 4 3 2 1 0
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|Byte1| MQTT控制报文类型 |指定控制报文类型的标志 |
+-----+-----------------------------------------------|
|Byte2| 剩余长度 |
+-----+-----------------------------------------------|
| ... | 剩余长度 |
+-----------------------------------------------------|
2.1 MQTT控制报文类型
MQTT的控制报文类型在固定报文头的第1个字节的4 ~ 7bit,共4位无符号值。这些值如下表描述:
类型 | 值 | 报文方向 | 描述 |
---|---|---|---|
RESERVED | 0 | 禁止 | 保留 |
CONNECT | 1 | 客户端到服务端 | 客户端请求连接服务器 |
CONNACK | 2 | 服务端到客户端 | 连接报文确认 |
PUBLISH | 3 | 双向 | 发布消息 |
PUBACK | 4 | 双向 | QoS1消息发布收到确认 |
PUBREC | 5 | 双向 | 发布收到(保证交付第一步) |
PUBREL | 6 | 双向 | 发布释放(保证交付第二步) |
PUBCOMP | 7 | 双向 | QoS2消息发布完成 |
SUBSCRIBE | 8 | 客户端到服务端 | 客户端订阅请求 |
SUBACK | 9 | 服务端到客户端 | 订阅请求报文确认 |
UNSUBSCRIBE | 10 | 客户端到服务端 | 客户端取消订阅请求 |
UNSUBACK | 11 | 服务端到客户端 | 取消订阅请求报文确认 |
PINGREQ | 12 | 客户端到服务端 | 心跳请求 |
PINGRESP | 13 | 服务端到客户端 | 心跳响应 |
DISCONNECT | 14 | 客户端到服务端 | 客户端断开连接 |
RESERVED | 15 | 禁止 | 保留 |
2.2 MQTT控制报文标志
MQTT的控制报文标志在固定报文头的第1个字节的4 ~ 7bit,包含每个MQTT报文类型的特定的标志。
注意:如果接收方收到非法的标志,接受者必须关闭网络连接。标志如下表:
其中:
- DUP:控制报文的重复分发标志
- QoS:PUBLISH报文的服务质量等级
- RETAIN:PUBLISH报文的保留标志
服务的质量等级包括:
- QoS0,At most once,至多一次;
- QoS1,At least once,至少一次;
- QoS2,Exactly once,确保只有一次。
类型 | 报文标志 | Bit3 | Bit2 | Bit1 | Bit0 |
---|---|---|---|---|---|
CONNECT | Reserved | 0 | 0 | 0 | 0 |
CONNACK | Reserved | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | DUP | QoS | QoS | RETAIN |
PUBACK | Reserved | 0 | 0 | 0 | 0 |
PUBREC | Reserved | 0 | 0 | 0 | 0 |
PUBREL | Reserved | 0 | 0 | 1 | 0 |
PUBCOMP | Reserved | 0 | 0 | 0 | 0 |
SUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
SUBACK | Reserved | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | Reserved | 0 | 0 | 1 | 0 |
UNSUBACK | Reserved | 0 | 0 | 0 | 0 |
PINGREQ | Reserved | 0 | 0 | 0 | 0 |
PINGRESP | Reserved | 0 | 0 | 0 | 0 |
DISCONNECT | Reserved | 0 | 0 | 0 | 0 |
2.2 MQTT报文剩余长度
剩余长度字段从固定报文头的第2个字节开始,最长可达4个字节,所以剩余长度访问是Byte[2 ~ 5]。
剩余长度表示当前报文剩余部分的字节数,包含可变头部和Payload(有效负载)。由于剩余长度字段的字节数是可变的,那么怎么确定其长度用几个字节来描述呢?答案:取决于字节的最高位Bit7;如果Bit7为1,那么需要继续计算字节长度,如果Bit7为0,那么不需要继续计算字节长度。
由于每个字节的最高位不表示数据长度所以,4个字节长度最大可以表示:128 * 128 * 128 * 128 Byte = 256MB
。
其消息长度范围如下表:
字节 | 最小值 | 最大值 |
---|---|---|
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) |
例如:可变头部和Payload(有效负载)的总字节数是300个字节。则剩余长度字段的表示如下:
10101100
00000010
关于剩余长度字段编解码的计算方法可以参考文章:http://www.jiajiajia.club/blog/artical/apil0e3qeqr1/515
三、mqtt可变报文头
某些控制报文包含可变报头,它在固定报头(Fixed header)和有效载荷(Payload)之间。每个协议的可变报头都不一样。其中大多数协议都会有的字段报文标识符。
具体可变报文头的内容以及有效负载和控制报文类型的关系,下面会有详细描述。
四、有效载荷(Payload)
有效载荷是除控制报文格式以为的有效信息,CONNECT、PUBLISH、SUBSCRIBE等需要传递有效信息的协议帧都需要。
五、不同控制报文类型的协议格式
5.1 CONNECT
CONNECT 协议是客户端建立连接的第一个报文,通常都要带有鉴权的字段,一个CONNECT报文都会对应一个服务端的CONNACK报文。
5.1.1可变报头:
可变报头需要10个字节。
- byte1-6表示协议名,第一个字节(MSB)为字符串长度最高有效字节数,第二个字节(LSB)为字符串长度最低有效字节数, 因为目前主流的CPU都是大端序(BigEndian),MSB和LSB两个字节组成的big int 表示协议名称的长度,即’MQTT’ 为4字节长度。
- byte7表示协议级别, 此处固定为 0x04。(表示v3.1.1)
- byte8表示连接标志,用1、0表示是否开启。
- 第0位为保留位,固定为0.
- 第1位为会话清理标志, 1表示每次建立连接,要求服务端重新开启会话;0则表示服务端会话持久化。一般选用1。
- 第2位为遗嘱标志位,1表示启用遗嘱功能,0则表示关闭遗嘱功能。启用时,连接标志中的Will Qos(第3、4位),Will Retain (第5位)会被使用,有效载荷中必须包含 遗嘱topic(Will Topic)、遗嘱消息(Will Message)字段;未启用时,连接标志中的Will Qos(第3、4位),Will Retain (第5位)必须为0,。一般选用0。
- 第3位、第4位用来表示遗嘱Qos,遗嘱标志位关闭时,强制填00,遗嘱标志位启用时,可以为00(qos0)、01(qos1)、10(qos2)
- 第5位为遗嘱保留标志位,1表示启用遗嘱保留,0表示关闭遗嘱保留。 遗嘱标志位关闭时,强制为0。
- 第6位为密码标志位,1表示启用密码,有效载荷中必须包含密码字段;0表示不启用密码,有效载荷中不能包含密码字段。正常都传密码。
- 第7位为用户名标志位,1表示启用用户名,有效载荷中必须包含用户名字段;0表示不启用用户名,有效载荷中不能包含用户名字段。正常都传用户名。如果不启用用户名时,密码也不能启用。
- byte9、byte10两个字节以MSB+LSB表示心跳间隔,单位为秒。客户端按照心跳间隔发送PINGREQ,服务端回PINGRESP,服务端在1.5倍心跳间隔没有收到PINGREQ时,将以keepalive timeout的理由断开连接。
5.1.2:关于遗嘱相关概念说明
当客户端启用遗嘱功能时,在CONNECT协议包的可变报头的连接标志中,必须打开遗嘱标志(byte8的第2位),并在有效载荷中传递遗嘱主题和遗嘱消息。
当服务端判断客户端异常断开时,服务端会向遗嘱主题发送遗嘱消息。
遗嘱消息发布的条件,包括但不限于:
- 服务端检测到了一个I/O错误或者网络故障。
- 客户端在保持连接(Keep Alive)的时间内未能通讯。
- 客户端没有先发送DISCONNECT报文直接关闭了网络连接。
- 由于协议错误服务端关闭了网络连接。
当启用遗嘱时,在标志位中可以设置遗嘱消息的Qos,即可变报头的byte8的第3、4位。
当启用遗嘱保留标志(byte8的第5位)时,表示遗嘱消息在发布时需要保留。 即启动遗嘱保留时,服务端要持久化该遗嘱消息(仅仅保留最新一条),其他人后订阅该遗嘱主题时,能收到最后一次的遗嘱消息。
5.1.3 有效载荷(Payload)
CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。
需要注意的是每一个字段的拼接方式都遵循 MSB+LSB+Content的格式。 即在字段内容前拼入两个字节表示的大端序bit int来表示字段的长度,Content内容必须是UTF-8格式。
案例:
mqttbox请求客户端:
二进制报文数据:
5.2 CONACK
CONNACK报文是确认连接报文。即CONNECT报文的响应报文,报文内容会返回连接成功标志。
5.2.1 可变报头
第1个字节是 连接确认标志,位7-1是保留位且必须设置为0。 第0 (SP)位 是当前会话(Session Present)标志。当服务端建立连接时,会话是新创建的时,SP返回0;会话是之前持久化的会话,且客户端传的清理会话标志没有开启时,SP返回1。
第2个字节为连接返回码。
5.3 PUBLISH
PUBLISH是发布消息协议报文,双向都可以使用。
5.3.1 固定报头
PUBLISH协议包的固定头会跟标志位进行变化。
第3位为重传标志,qos0以上的PUBLISH是要求对方返回ack的,没有收到ack时,会被重传,重传时,DUP位为1。所以qos不能传1。
第2-1位为Qos标志,用两位表示qos 0、1、2,与CONNECT可变报头的连接标志一样。
第0位为保留消息标志,同样1表示启用,0表示不启用。这里的保留消息与前面提到遗嘱保留消息差不多,都是指,要求服务端收到PUBLISH时,持久化最新的一条消息,以便后来订阅主题的连接能够收到最近的一条消息。
5.3.2 可变报头
可变报头按顺序包含主题名和报文标识符。
- 主题名:拼接方式均为 MSB+LSB+Content,主题名不能包含通配符。
- 报文标识符: Qos为1,2时,才传报文标识符。 由MSB+LSB组成的int值表示, 报文标识符自增id,每次建立连接后从1开始。
5.3.3 有效载荷
有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度。包含零长度有效载荷的PUBLISH报文是合法的。
5.4 PUBACK
PUBACK报文是对QoS 1等级的PUBLISH报文的响应。
5.4.1 可变报头
可变报头仅有报文标识符。格式为 MSB+LSB。
5.4.2 有效载荷
PUBACK报文没有有效载荷。
5.5 PUBREC
PUBREC报文是对QoS等级2的PUBLISH报文的响应。它是QoS 2等级协议交换的第二个报文。
5.5.1 可变报头
可变报头包含等待确认的PUBLISH报文的报文标识符。格式为 MSB+LSB。
5.5.2 有效载荷
PUBREC报文没有有效载荷。
5.6 PUBREL
PUBREL报文是对PUBREC报文的响应。它是QoS 2等级协议交换的第三个报文。
5.6.1 可变报头
可变报头包含与等待确认的PUBREC报文相同的报文标识符。格式为 MSB+LSB。
5.6.2 有效载荷
PUBREL报文没有有效载荷。
5.7 PUBCOMP
PUBCOMP报文是对PUBREL报文的响应。它是QoS 2等级协议交换的第四个也是最后一个报文。
5.7.1 可变报头
可变报头包含与等待确认的PUBREL报文相同的报文标识符。格式为 MSB+LSB。
5.7.2 有效载荷
PUBCOMP报文没有有效载荷。
5.8 SUBSCRIBE
客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。
5.8.1 可变报头
可变报头仅有报文标识符。格式为 MSB+LSB。
5.8.2 有效载荷
SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表,它们表示客户端想要订阅的主题。服务端支持主题过滤器通配订阅。每一个过滤器后面跟着一个字节,这个字节被叫做 服务质量要求(Requested QoS)。它给出了服务端向客户端发送应用消息所允许的最大QoS等级。
SUBSCRIBE报文的有效载荷必须包含至少一对主题过滤器 和 QoS等级字段组合。
每一组主题过滤器和QoS组合的拼接格式为:MSB+LSB+Content+Qos,Qos用一个字节表示,可能的值为 0x00、0x01、0x02
5.9 SUBCK
服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文。SUBACK报文包含一个返回码清单,它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。
5.9.1 可变报头
可变报头包含等待确认的SUBSCRIBE报文的报文标识符。格式为 MSB+LSB。
5.9.2 有效载荷
有效载荷包含一个返回码清单。每个返回码对应等待确认的SUBSCRIBE报文中的一个主题过滤器。返回码的顺序必须和SUBSCRIBE报文中主题过滤器的顺序相同
允许的返回码值:
- 0x00 最大QoS 0
- 0x01 成功 最大QoS 1
- 0x02 成功 最大 QoS 2
- 0x80 Failure 失败
- 0x00, 0x01, 0x02, 0x80之外的SUBACK返回码是保留的,不能使用
5.10 UNSUBSCRIBE
客户端发送UNSUBSCRIBE报文给服务端,用于取消订阅主题。
5.10.1 可变报头
可变报头仅有报文标识符。格式为 MSB+LSB。
5.10.2 有效载荷
UNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。每一个主题过滤器的拼接格式为:MSB+LSB+Content
5.11 UNSUBCK
服务端发送UNSUBACK报文给客户端用于确认收到UNSUBSCRIBE报文。
5.11.1 可变报头
可变报头仅有报文标识符。格式为 MSB+LSB。
5.11.2 有效载荷
UNSUBACK报文没有有效载荷。
5.12 PINGREQ
客户端发送PINGREQ报文给服务端的心跳包。
5.12.1 可变报头
PINGREQ报文没有可变报头。
5.12.2 有效载荷
PINGREQ报文没有有效载荷。
5.13 PINGRESP
服务端发送PINGRESP报文给客户端的心跳响应包。
5.13.1 可变报头
PINGRESP报文没有可变报头。
5.13.2 有效载荷
PINGRESP报文没有有效载荷。
5.14 DISCONNECT
DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。
5.14.1 可变报头
DISCONNECT报文没有可变报头。
5.14.2 有效载荷
DISCONNECT报文没有有效载荷。