通过 MQTT 学习协议制定与实现(一)

482 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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 控制报文都含有一个固定报头,固定报头格式:

image.png

控制报文类型: 第一个字节,7-4位,表示为4位无符号值,控制报文类型定义如表格:

名字报文流动方向描述
Reserved0禁止保留位
CONNECT1客户端到服务端客户端请求连接服务端
CONNACK2服务端到客户端连接报文确认
PUBLISH3两个方向都允许发布消息
PUBACK4两个方向都允许Qos 1 消息发收到确认
PUBREC5两个方向都允许发布收到(保证交付第一步)
PUBREL6两个方向都允许发布释放(保证交付第二步)
PUBCOMP7两个方向都允许QoS 2 消息发布完成(保证交互完成)
SUBSCRIBE8客户端到服务端客户端订阅请求
SUBACK9服务端到客户端订阅请求报文确认
UNSUNSCRIBE10客户端到服务端客户端取消订阅请求
UNSUBACK11服务端到客户端取消订阅确认
PINGREQ12客户端到服务端心跳请求
PINGRESP13服务端到客户端心跳响应
DISCONNECT14客户端到服务端客户端断开连接
Reserved15禁止保留位

标志: 固定头部第一个字节的剩余的4位[3-0]包含每个 MQTT 控制报文类型的特定的标志,如果收到非法标志,接收者必须关闭网络连接。

控制报文固定报头标志Bit 3Bit 2Bit 1Bit 0
CONNECTReserved0000
CONNACKReserved0000
PUBLISHUsed in MQTT 3.1.1DUP1QoS2QoS2RETAIN3
PUBACKReserved0000
PUBRECReserved0000
PUBRELReserved0010
PUBCOMPReserved0000
SUBSCRIBEReserved0010
SUBACKReserved0000
UNSUBSCRIBEReserved0010
UNSUBACKReserved0000
PINGREQReserved0000
PINGRESPReserved0000
DISCONNECTReserved0000

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

剩余长度字段使用一个变长度编码方案,对小于 128 的值它使用单字节编码,更大的值按下面的方式处理。

字节数最小值最大值
10 (0x00)127 (0x7F)
2128 (0x80, 0x01)16 383 (0xFF, 0x7F)
316 384 (0x80, 0x80, 0x01)2 097 151 (0xFF, 0xFF, 0x7F)
42 097 152 (0x80, 0x80, 0x80, 0x01)268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)

可变报头

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

报文标识符 image-20221123221624374.png

很多控制报文的可变报头部分包含一个两字节的报文标识符字段。这些报文是: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不需要