16 MQTT协议分析(一)

16 MQTT协议分析

16.1 mqtt协议介绍

16.1.1 概述

​ MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。

16.1.2 特点

a) 开放消息协议,简单易实现

b) 发布订阅模式,一对多消息发布

c) 基于TCP/IP网络连接

d) 1字节固定报头,2字节心跳报文,报文结构紧凑

e) 消息QoS支持,可靠传输保证

16.1.3 应用

​ MQTT协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等领域。

a) 物联网M2M通信,物联网大数据采集

b) Android消息推送,WEB消息推送

c) 移动即时消息,例如Facebook Messenger

d) 智能硬件、智能家具、智能电器

e) 车联网通信,电动车站桩采集

f) 智慧城市、远程医疗、远程教育

g) 电力、石油与能源等行业市场

16.2 mqtt协议报文格式组成

16.2.1 mqtt控制报文结构

MQTT 协议通过交换预定义的 MQTT 控制报文来通信。 这一节描述这些报文的格式。MQTT 控制报文由三部分组成,如下图:

16.2.2 mqtt固定报头

​ 每个 MQTT 控制报文都包含一个固定报头, 固定报头的格式如下图:

16.2.3 mqtt控制报文类型

​ 位置: 第 1 个字节, 二进制位 7-4,表示为 4 位无符号值。

​ MQTT 控制报文的类型:如下表:

名字 报文流动方向 描述
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 禁止 保留

16.2.4 标记

​ 固定报头第 1 个字节的剩余的 4 位 [3-0]包含每个 MQTT 控制报文类型特定的标志 。标记位说明如下表所示:

控制报文 固定报头标志 Bit 3 Bit 2 Bit 1 Bit 0
CONNECT Reserved 0 0 0 0
CONNACK Reserved 0 0 0 0
PUBLISH Used in MQTT 3.1.1 DUP1 QoS2 QoS2 RETAIN3
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

DUP1 =控制报文的重复分发标志
QoS2 = PUBLISH 报文的服务质量等级
RETAIN3 = PUBLISH 报文的保留标志

16.2.5 剩余长度

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

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

​ 例如, 十进制数 64 会被编码为一个字节, 数值是 64, 十六进制表示为 0x40,。十进制数字321(=65+2*128)被编码为两个字节, 最低有效位在前。 第一个字节是 65+128=193。 注意最高位为
1 表示后面至少还有一个字节。 第二个字节是 2。

16.2.5.1 示例

123456  = 964 x 128 + 64 

964 = 7x128 + 68

7 < 128

也就是123456 = (7 x 128 + 68)x128 + 64

展开:64 + 68 x128 + 7x128x128 

第一字节:64 | 0x80 = x (0x80=0x1000 0000或上最高位表示是否还有更多的字节)

第二字节:68 | 0x80 = y (0x80=0x1000 0000或上最高位表示是否还有更多的字节)

第三字节:7=z

c语言表示:unsigned char len_byte[4] = { 64 | 128 , 68 | 128,  7  , 0 }

反过来,如果要算出123456

x-128 + (y-128)*128 + z x 128 x 128 

把剩余长度转换成字节表示:

把字节转换成剩余长度表示:

16.2.6 可变报头

​ 某些 MQTT 控制报文包含一个可变报头部分。 它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。报文标识符是可变报头一种,可变报头的报文标识符( Packet Identifier) 字段存在于在多个类型的报文里。

报文标识符类型如下图:
image

​ 很多控制报文的可变报头部分包含一个两字节的报文标识符字段。 这些报文是 PUBLISH( QoS>0 时) ,PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCIBE,UNSUBACK,如下表所示:

控制报文 报文标识符字段
CONNECT 不需要
CONNACK 不需要
PUBLISH 需要(如果QoS > 0)
PUBACK 需要
PUBREC 需要
PUBREL 需要
PUBCOMP 需要
SUBSCRIBE 需要
SUBACK 需要
UNSUBSCRIBE 需要
UNSUBACK 需要
PINGREQ 不需要
PINGRESP 不需要
DISCONNECT 不需要

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

​ 例如,当client发送一个packet Identifier =0x1234的报文给server时,server的回复报文packet identifier 必须是0x1234,Packet identifier 从1开始递增,到达65535时,又从1开始计算。

16.2.7 有效载荷

​ 某些 MQTT 控制报文在报文的最后部分包含一个有效载荷,带有有效载荷报文类型如下表所示:

控制报文 有效载荷
CONNECT 需要
CONNACK 不需要
PUBLISH 可选
PUBACK 不需要
PUBREC 不需要
PUBREL 不需要
PUBCOMP 不需要
SUBSCRIBE 需要
SUBACK 需要
UNSUBSCRIBE 需要
UNSUBACK 不需要
PINGREQ 不需要
PINGRESP 不需要
DISCONNECT 不需要