## 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 控制报文由三部分组成,如下图:
![图2.1 mqtt报文组成]()
### 16.2.2 mqtt固定报头
每个 MQTT 控制报文都包含一个固定报头, 固定报头的格式如下图:
![图2.2 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) 表示当前报文剩余部分的字节数, 包括可变报头和负载的数据。 剩余长度不包括用于编码剩余长度字段本身的字节数。
![图2.3 剩余长度包含的报文范围]()
剩余长度字段使用一个变长度编码方案, 对小于 128 的值它使用单字节编码。 更大的值按下面的方式处理。低 7 位有效位用于编码数据,最高有效位用于指示是否有更多的字节。 因此每个字节可以编码 128 个数值和一个延续位( continuation bit) 。 剩余长度字段最大 4 个字节。
例如, 十进制数 64 会被编码为一个字节, 数值是 64, 十六进制表示为 0x40,。十进制数字321(=65+2*128)被编码为两个字节, 最低有效位在前。 第一个字节是 65+128=193。 注意最高位为
1 表示后面至少还有一个字节。 第二个字节是 2。
#### 16.2.5.1 示例
```c
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) 字段存在于在多个类型的报文里。
报文标识符类型如下图:
![图2.4 报文标识符]()
很多控制报文的可变报头部分包含一个两字节的报文标识符字段。 这些报文是 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开始计算。
![图2.5 需要 Packet Identifier 的报文类型交互示意图]()
### 16.2.7 有效载荷
某些 MQTT 控制报文在报文的最后部分包含一个有效载荷,带有有效载荷报文类型如下表所示:
| **控制报文** | **有效载荷** |
| --------------- | ------------ |
| **CONNECT** | 需要 |
| **CONNACK** | 不需要 |
| **PUBLISH** | 可选 |
| **PUBACK** | 不需要 |
| **PUBREC** | 不需要 |
| **PUBREL** | 不需要 |
| **PUBCOMP** | 不需要 |
| **SUBSCRIBE** | 需要 |
| **SUBACK** | 需要 |
| **UNSUBSCRIBE** | 需要 |
| **UNSUBACK** | 不需要 |
| **PINGREQ** | 不需要 |
| **PINGRESP** | 不需要 |
| **DISCONNECT** | 不需要 |
|