开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
写在前面
如果你做过物联网通信业务(IoT)那你一定接触过 MQTT 协议,轻巧、简单、规范是我学习使用 MQTT 协议的第一印象,使用 MQTT 协议做过一段时间业务之后,想通过两篇文章来介绍 MQTT 3.1.1协议,第一篇文章总结 MQTT 协议制定;第二篇文章使用 Netty 对 MQTT 协议的实现一个 MQTT Broker 通过 Wireshark 抓包来分析一下 MQTT 报文。
MQTT 概述
MQTT(Message Queuing Telemetry Transport) 最初是 MQ Telemetry Transport 的缩写,源自 IBM MQ 系列中间件产品,是一个客户端服务端架构的轻量级、发布/订阅模式、机器到机器的消息传输协议,由 IBM 在1999年发布,用于消息队列服务。它的设计思想是轻巧、开放、简单、规范,因此易于实现。这些特点使得它对很多场景来说都是很好的选择,包括受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT),这些场景要求很小的代码封装或者网络带宽非常昂贵。
MQTT 运行在 TCP/IP 协议或者其他提供有序、可靠、双向连接上,有如下特点:
-
使用发布/订阅消息模式,提供一对多的消息分发和应用之间解耦。
-
消息传输不需要知道负载(payload)内容。
-
提供三种等级的服务质量 QoS(Quality of Service):
- QoS=0 最多传输一次,尽操作环境所能提供的最大努力分发消息,消息可能会丢失。例如,这个等级可用于环境传感器数据,单次的数据丢失没关系,因为不久之后会再次发送。
- QoS=1 至少传输一次,保证消息可以到达,但是可能会重复。
- QoS=2 仅一次,保证消息只到达一次。例如,这个等级可用在一个计费系统中,这里如果消息重复或丢失会导致不正确的收费。
-
很小的传输消耗和协议数据交换,最大限度减少网络流量。
-
异常连接断开发生时,能通知到相关各方。
MQTT 控制报文
MQTT 协议通过交换预定义的 MQTT 控制报文来通信,MQTT 控制报文由三部分组成,
| Fixed header 固定报头,所有控制报文都包含 |
|---|
| Variable header 可变报头,部分控制报文包含 |
| Payload 有效载荷,部分控制报文包含 |
固定头部
每个 MQTT 控制报文都含有一个固定报头,固定报头格式:
控制报文类型: 第一个字节,7-4位,表示为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 | 服务端到客户端 | 订阅请求报文确认 |
| UNSUNSCRIBE | 10 | 客户端到服务端 | 客户端取消订阅请求 |
| UNSUBACK | 11 | 服务端到客户端 | 取消订阅确认 |
| PINGREQ | 12 | 客户端到服务端 | 心跳请求 |
| PINGRESP | 13 | 服务端到客户端 | 心跳响应 |
| DISCONNECT | 14 | 客户端到服务端 | 客户端断开连接 |
| Reserved | 15 | 禁止 | 保留位 |
标志: 固定头部第一个字节的剩余的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 |
剩余长度(Remaining Length): 表示当前报文剩余部分的字节数,包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。
剩余长度字段使用一个变长度编码方案,对小于 128 的值它使用单字节编码,更大的值按下面的方式处理。
| 字节数 | 最小值 | 最大值 |
|---|---|---|
| 1 | 0 (0x00) | 127 (0x7F) |
| 2 | 128 (0x80, 0x01) | 16 383 (0xFF, 0x7F) |
| 3 | 16 384 (0x80, 0x80, 0x01) | 2 097 151 (0xFF, 0xFF, 0x7F) |
| 4 | 2 097 152 (0x80, 0x80, 0x80, 0x01) | 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F) |
可变报头
某些 MQTT 控制报文包含一个可变报头部分,它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里。
报文标识符
很多控制报文的可变报头部分包含一个两字节的报文标识符字段。这些报文是:PUBLISH(QoS>0),PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE,SUBACK,UNSUBSCIBE,UNSUBACK
有效载荷
某些 MQTT 控制报文在报文的最后部分包含一个有效载荷,对于 PUBLISH 来说有效载荷就是应用消息,包含有效载荷的控制报文:
| 控制报文 | 有效载荷 |
|---|---|
| CONNECT | 需要 |
| CONNACK | 不需要 |
| PUBLISH | 可选 |
| PUBACK | 不需要 |
| PUBREC | 不需要 |
| PUBREL | 不需要 |
| PUBCOMP | 不需要 |
| SUBSCRIBE | 需要 |
| SUBACK | 需要 |
| UNSUBSCRIBE | 需要 |
| UNSUBACK | 不需要 |
| PINGREQ | 不需要 |
| PINGRESP | 不需要 |
| DISCONNECT | 不需要 |