理解MQTT 协议
1. 概述
MQTT(Message Queuing Telemetry Transport 消息队列遥测传输协议) 是一种应用层的消息传输协议,通常用于物联网(IoT)和传感器网络中进行通信。它被设计用于在低带宽、不稳定或高延迟的网络环境下传输数据,因此非常适用于物联网设备之间的通信,尤其在资源有限的环境中。
MQTT 的主要特点:
- 轻量级
面向物联网环境,设计精简,数据包占用空间小,协议易于实现,能运行在各种嵌入式设备。 - 发布/订阅模式
MQTT 协议的一个关键特性是发布和订阅模型,方便消息在传感器之间传递,一对多消息发布。
与所有消息协议一样,它将数据的发布者与使用者分离。
在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。应用消息通过MQTT传输时,它们有关联的服务质量(QoS)和主题(Topic)。 - 可靠性
MQTT 协议通过定义的QoS服务质量确保消息投递和消费。 - 持久会话
客户端和服务端建立连接之后会形成一个会话,通过本地持久化消息管理会话 - 适应性(可扩展)
数据包体(payload)用UTF-8编码,扩展性强。
1.1 协议版本和发展史
MQTT最初由IBM于20世纪90年代发明,该协议的发明人是的Andy Stanford-Clark和Arlen Nipper。最初是用于石油管道的传感器与卫星之间数据传输。
OASIS(结构化信息标准促进组织)
2014年10月29日,MQTT成为OASIS(结构化信息标准促进组织)正式批准的通讯标准。OASIS是一个推进电子商务标准的发展、融合与采纳的非盈利性国际化组织。相比其他组织,OASIS形成了Web服务标准的同时也提出了安全的电子商务标准,同时在针对公众领域和特定应用市场的标准化方面也付出很多的努力。自1993年成立开始,OASIS已经发展成为了由来自100多个国家的600多家组织、企业。简言之,由众多业内专家组成的OASIS愿意为MQTT背书,组件该协议在物联网领域的重要性。
目前MQTT主流版本有两个,分别是MQTT3.1.1和MQTT5。MQTT3.1.1是在2014年10月发布的,而MQTT5是在2019年3月发布的。
MQTT5是在MQTT3.1.1的基础上进行了升级。因此MQTT5是完全兼容MQTT3.1.1的。而MQTT5是在MQTT3.1.1的基础上添加了更多的功能补充完善MQTT协议。
1.2 发布/订阅机制
消息的发布订阅一般有3个参与方
- Publisher 消息发布者
- Broker(MqttServer) 代理服务器
- Subscriber 消息订阅者
1.3 QoS 机制概述
Qos 是 Quality of Service 的缩写,表示服务质量,MQTT的QoS 有3个等级,对应值 0,1,2 有两个bit表示,服务质量依次升高。
QoS value | bit2 | bit1 | 描述 |
---|---|---|---|
0 | 0 | 0 | 最多发送一次,不管接收方是否收到 |
1 | 0 | 1 | 最少发送一次,保证接收方一定会收到,但是接收方可能会收到重复消息 |
2 | 1 | 0 | 仅成功发送一次消息,保证消息发送成功,并且接收方只消费一次 |
MQTT 通过在数据包头定义的消息类型中实现该机制,概述如下:
-
QoS0 消息只发送一次(Publisher---->Broker),消息类型 PUBLISH
-
QoS1 消息至少发送一次,通过立即发送PUBACK应答消息进行确认,消息已被接收。此场景中,发送方发出PUBLISH包后需要本地存储该数据包,并且等待PUBACK应答,如果一段时间没有收到PUBACK消息,将进行重发(消息标识符重发标志为1)
Sender(发送方)---PUBLISH--> Reciver
Sender(发送方)<---PUBACK--- Reciever -
QoS2 成功发送一次,确保接收方已经收到不重复的消息,主要过程如下:
Sender(发送方)---PUBLISH--> Reciver
Sender(发送方)<---PUBREC--- Reciever
Sender(发送方)---PUBREL--> Reciver
Sender(发送方)<---PUBCOMP--- Reciever
1.Sender 发送QoS2 的PUBLISH数据包,并本地保存
2.Receiver 收到PUBLISH数据包之后,本地保存,并回复PUBREC数据包
3.当Sender 收到PUBREC数据包之后,可以安全的丢弃初始PUBLISH数据包,并保存PUBREC数据包,同时再回复Receiver 一个PUBREL数据包
4.当Receiver收到PUBREL数据包后,可以丢掉保存的PUBLISH包,并回复一个PUBCOMP包
5.只有当Sender收到PUBCOMP数据包,才能认为传输已经完成,并丢弃对应的PUBREC数据包
要实现上述过程,发送方和接收方都需要心跳重发与本地存储。
2. MQTT 数据包格式(对v3.1.1协议内容的理解)
每个MQTT 数据包包含三个部分(协议内容Figure2.1)。
- 固定头 Fixed
- 可变头 Variable
- 包体 Payload
2.1 固定头 FixedHeader
固定包头有两个部分,分别定义了消息类型和内容长度;协议设计出于精简性和可扩展性考虑,固定头字节数不固定,第一个字节用于定义类型,第2-N个字节来定义长度。
第一个字节的高4bit 用于定义消息类型,低4位定义特定消息的相关标识。
Bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
byte 1 |
MQTT Control Packet type |
Flags specific to each MQTT Control Packet type |
||||||
byte 2… |
Remaining Length |
2.1.1 固定报头的消息类型MQTTControl Packet type和 标识Flags
协议总共定义了14中消息类型
Name | Value | 方向 | 描述 |
---|---|---|---|
保留 | 0 | -- | -- |
CONNECT | 1 | C-->S | 客户端发起连接请求 |
CONNACK | 2 | S-->C | 连接请求应答 |
PUBLISH | 3 | C<-->S | 发布消息 |
PUBACK | 4 | C<-->S | 发布消息请求应答 |
PUBREC | 5 | C<-->S | 发布接收(保证交付第一部分) |
PUBREL | 6 | C<-->S | 发布释放(保证交付第二部分) |
PUBCOMP | 7 | C<-->S | 发布完成(保证交付第三部分) |
SUBSCRIBE | 8 | C-->S | 订阅请求 |
SUBACK | 9 | S-->C | 订阅请求应答 |
UNSUBSCRIBE | 10 | C-->S | 取消订阅请求 |
UNSUBACK | 11 | S-->C | 取消请阅请求应答 |
PINGREQ | 12 | C-->S | PING |
PINGRESP | 13 | S-->C | PING 应答 |
DISCONNECT | 14 | C-->S | 客户端离线 |
保留 | 15 | -- | -- |
Flags
消息类型 | Flags | bit3 | bit2 | bit1 | bit0 |
---|---|---|---|---|---|
CONNECT | 保留 | 0 | 0 | 0 | 0 |
CONNACK | 保留 | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | DUP1 | QoS2 | QoS2 | RETAIN3 |
PUBACK | 保留 | 0 | 0 | 0 | 0 |
PUBREC | 保留 | 0 | 0 | 0 | 0 |
PUBREL | 保留 | 0 | 0 | 1 | 0 |
PUBCOMP | 保留 | 0 | 0 | 0 | 0 |
SUBSCRIBE | 保留 | 0 | 0 | 1 | 0 |
SUBACK | 保留 | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | 保留 | 0 | 0 | 1 | 0 |
UNSUBACK | 保留 | 0 | 0 | 0 | 0 |
PINGREQ | 保留 | 0 | 0 | 0 | 0 |
PINGRESP | 保留 | 0 | 0 | 0 | 0 |
DISCONNECT | 保留 | 0 | 0 | 0 | 0 |
DUP1 = PUBLISH包重发标识
QoS2 = QoS标识
RETAIN3 = PUBLISH 保留标志
2.1.2 固定包头剩余长度 Remaining Length
从固定报头的第二个字节开始(非规范定义:剩余长度字节数一般不超过4),表示该数据包的长度,该长度不包括用于编码剩余长度的字节。
剩余长度使用可变长度编码方案进行二进制编码,每个字节低7位是有效位,所以单字节最大值127,最高位用于指示存在后续字节。因此每个字节编码128个值和一个“连续位”。
例如:
十进制的64, 编码为一个字节 0x40
十进制的321(=65+2*128) 编码为2个字节,低有效字节在前。第一个字节是 0xE5 0x02
剩余长度数值范围
字节数 | 最小 | 最大 |
---|---|---|
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) |
2.2 可变包头 Variable header
可变包头可以看作是对消息类型的补充定义,内容取决于消息类型,有些消息类型没有可变包头。
控制报文 | 报文标识符字段 |
---|---|
CONNECT | 不需要 |
CONNACK | 不需要 |
PUBLISH | 需要(如果QoS > 0) |
PUBACK | 需要 |
PUBREC | 需要 |
PUBREL | 需要 |
PUBCOMP | 需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
2.3 数据包体(Payload)
有些消息类型不需要Payload,有些类型可以作为可选部分,体现了协议的扩展性。
Control Packet |
Payload |
CONNECT |
Required |
CONNACK |
None |
PUBLISH |
Optional |
PUBACK |
None |
PUBREC |
None |
PUBREL |
None |
PUBCOMP |
None |
SUBSCRIBE |
Required |
SUBACK |
Required |
UNSUBSCRIBE |
Required |
UNSUBACK |
None |
PINGREQ |
None |
PINGRESP |
None |
DISCONNECT |
None |