文章目录
MQTT概念
MQTT是一种轻量级的,基于发布-订阅模式的消息传输协议,适用于资源受限的设备和低带宽网络环境
MQTT优点
轻量级:MQTT开销小,报文小
可靠:MQTT 支持多种 QoS 等级、会话感知和持久连接,即使在困难的条件下也能保证消息的可靠传递
MQTT工作原理
要了解 MQTT 的工作原理,首先需要掌握以下几个概念:MQTT 客户端、MQTT Broker、发布-订阅模式、主题、QoS
MQTT客户端
使用MQTT协议进行通信的设备或应用程序
MQTT Broker 【MQTT服务端】
负责处理客户端请求的关键组件,包括建立链接,断开链接,订阅和取消订阅等操作,同时负责消息的转发。
发布-订阅模式
发布-订阅模式与客户端-服务器模式的不同之处在于,它将发送消息的客户端(发布者)和接收消息的客户端(订阅者)进行了解耦。发布者和订阅者之间无需建立直接连接,而是通过 MQTT Broker 来负责消息的路由和分发
假设场景:
有一台客户端 发布了摄氏度为20° 的消息,Broker接收后,将其发送给该订阅主题上的设备 ,如下图所示
主题
MQTT 协议根据主题来转发消息。主题通过 / 来区分层级
示例:
test/test/1
test2/2/test
QOS
MQTT 提供了三种服务质量(QoS),在不同网络环境下保证消息的可靠性。
- QoS 0:消息最多传送一次。如果当前客户端不可用,它将丢失这条消息。
- QoS 1:消息至少传送一次。
- QoS 2:消息只传送一次。
MQTT工作流程
- 客户端使用socket 链接 Broker。并向客户端提供认证信息 一般是user和password,并指定会话类型(Clean Session 或 Persistent Session)
- 客户端向Broker 发布消息 或者订阅主题以接收信息
- MQTT Broker接收发布的消息,并转发给订阅对应主题的客户端
PS:什么是Clean Session 或 Persistent Session?
持久会话是指客户端与 MQTT 代理之间的会话在客户端断开连接时不会立即终止。当客户端再次连接到代理时,代理会恢复之前的会话状态,包括已订阅的主题和 QoS(Quality of Service)级别。
1.保持会话状态:即使客户端断开连接,代理仍然会保留会话信息。当客户端再次连接到代理时,代理会恢复之前的订阅状态。
2.自动重新连接:当网络连接中断时,代理会自动尝试重新连接客户端,从而保持消息传递的连续性。
持久会话的缺点:
1.存储开销:由于代理需要存储每个客户端的会话状态,因此对于大量客户端,可能会增加存储开销。
2.消息顺序问题:由于持久会话需要保持会话状态,如果客户端在长时间未连接后重新连接,可能会接收到不按顺序的消息。
非持久会话是指客户端与 MQTT 代理之间的会话在客户端断开连接时立即终止。每次客户端连接到代理时,代理都会创建一个新的会话,没有任何之前的订阅状态。
非持久会话的优点:
1.轻量级:非持久会话不需要存储会话状态,因此对于大量客户端来说,存储开销较小。
2.消息顺序保证:由于非持久会话没有保持会话状态,因此消息传递的顺序更加可靠。
非持久会话的缺点:
1.需要重新订阅:每次客户端连接到代理时,都需要重新订阅感兴趣的主题,这可能会增加网络流量和延迟。
2.无法自动重新连接:由于非持久会话没有保持会话状态,因此当网络连接中断时,客户端需要手动重新连接代理
MQTT 客户端通常只能在在线状态下接收其它客户端发布的消息。如果客户端离线后重新上线,它将无法收到离线期间的消息。
但是,如果客户端连接时设置 Clean Session 为 false,并且使用相同的客户端 ID 再次上线,那么消息服务器将为客户端缓存一定数量的离线消息,并在它重新上线时发送给它。
遗嘱信息
MQTT 客户端在向服务器发起 CONNECT 请求时,可以选择是否发送遗嘱消息标志,并指定遗嘱消息的主题和有效载荷。
如果 MQTT 客户端异常离线(在断开连接前没有向服务器发送 DISCONNECT 消息),MQTT 服务器会发布遗嘱消息。
QOS
MQTT中的QoS等级
MQTT设计了一套保证消息稳定传输的机制,包括消息应答、存储和重传。在这套机制下,提供了三种不同层次QoS(Quality of Service):
- QoS0,At most once,至多一次;
- QoS1,At least once,至少一次;
- QoS2,Exactly once,确保只有一次。
QoS0 代表,Sender 发送的一条消息,Receiver 最多能收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,也就算了;
QoS1 代表,Sender 发送的一条消息,Receiver 至少能收到一次,也就是说 Sender 向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,但是因为重传的原因,Receiver 有可能会收到重复的消息;
QoS2 代表,Sender 发送的一条消息,Receiver 确保能收到而且只收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,同时保证 Receiver 不会因为消息重传而收到重复的消息。
QOS0下的通信流程
Sender向Receiver发送一个包含消息数据的PUBLISH包,然后不管结果如何,丢掉已发送的PUBLISH包,一条消息的发送完成。
QOS1下的通信流程
QoS1要保证消息至少到达一次,所以有一个应答的机制。Sender和Receiver的一次消息的传递流程如下:
Sender向Receiver发送一个带有数据的PUBLISH包,并在本地保存这个PUBLISH包;
Receiver收到PUBLISH包以后,向Sender发送一个PUBACK数据包,PUBACK数据包没有消息体(Payload),在可变头中有一个包标识(Packet Identifier),和它收到的PUBLISH包中的Packet Identifier一致。
Sender收到PUBACK之后,根据PUBACK包中的Packet Identifier找到本地保存的PUBLISH包,然后丢弃掉,一次消息的发送完成。
QOS2下的通信流程
相比QoS0和QoS1,QoS2不仅要确保Receiver能收到Sender发送的消息,还需要确保消息不重复。它的重传和应答机制就要复杂一些,同时开销也是最大的。
- Sender发送QoS为2的PUBLISH数据包,数据包 Packet Identifier 为 P,并在本地保存该PUBLISH包;
- Receiver收到PUBLISH数据包后,在本地保存PUBLISH包的Packet Identifier P,并回复Sender一个PUBREC数据包,PUBREC数据包可变头中的Packet Identifier为P,没有消息体(Payload);
- 当Sender收到PUBREC,它就可以安全的丢弃掉初始Packet Identifier为P的PUBLISH数据包。同时保存该PUBREC数据包,并回复Receiver一个PUBREL数据包,PUBREL数据包可变头中的Packet Identifier为P,没有消息体;
- 当Receiver收到PUBREL数据包,它可以丢掉保存的PUBLISH包的Packet Identifier P,并回复Sender一个可变头中 Packet Identifier 为 P,没有消息体(Payload)的PUBCOMP数据包;
- 当Sender收到PUBCOMP包,那么认为传输已完成,则丢掉对应的PUBREC数据包;
MQTT 控制报文
MQTT 目前定义了 15 种控制报文类型,如果按照功能进行分类,我们可以将这些报文分为连接、发布、订阅三个类别
CONNECT 报文用于客户端向服务端发起连接,CONNACK 报文则作为响应返回连接的结果。如果想要结束通信,或者遇到了一个必须终止连接的错误,客户端和服务端可以发送一个 DISCONNECT 报文然后关闭网络连接。
AUTH 报文是 MQTT 5.0 引入的全新的报文类型,它仅用于增强认证,为客户端和服务端提供更安全的身份验证。
PINGREQ 和 PINGRESP 报文用于连接保活和探活,客户端定期发出 PINGREQ 报文向服务端表示自己仍然活跃,然后根据 PINGRESP 报文是否及时返回判断服务端是否活跃。
PUBLISH 报文用于发布消息,余下的四个报文分别用于 QoS 1 和 2 消息的确认流程。
SUBSCRIBE 报文用于客户端向服务端发起订阅,UNSUBSCRIBE 报文则正好相反,SUBACK 和 UNSUBACK 报文分别用于返回订阅和取消订阅的结果
MQTT 报文格式
在 MQTT 中,无论是什么类型的控制报文,它们都由固定报头、可变报头和有效载荷三个部分组成。
固定报头固定存在于所有控制报文中,而可变报头和有效载荷是否存在以及它们的内容则取决于具体的报文类型。例如用于维持连接的 PINGREQ 报文就只有一个固定报头,用于传递应用消息的 PUBLISH 报文则完整地包含了这三个部分。
固定报头
固定报头由报文类型、标识位和报文剩余长度三个字段组成。
报文类型位于固定报头第一个字节的高 4 位,它是一个无符号整数,很显然,它表示当前报文的类型
固定报头第一个字节中剩下的低 4 位包含了由控制报文类型决定的标识位。只有 PUBLISH 报文的这四个比特位被赋予了明确的含义:
Bit 3:DUP,表示当前 PUBLISH 报文是否是一个重传的报文。
Bit 2,1:QoS,表示当前 PUBLISH 报文使用的服务质量等级。
Bit 0:Retain,表示当前 PUBLISH 报文是否是一个保留消息。
可变字节整数
固定报头长度并不是固定的,为了尽可能地减少报文大小,MQTT 将剩余长度字段设计成了一个可变字节整数。
在 MQTT 中,存在很多长度不确定的字段,例如 PUBLISH 报文中的 Payload 部分就用来承载实际的应用消息内容,而应用消息的长度显然是不固定的。所以我们需要一个额外的字段来指示这些不定长内容的长度,以便接收端正确地解析。
可变报头
可变报头的内容取决于具体的报文类型。例如 CONNECT 报文的可变报头按顺序包含了协议名、协议级别、连接标识、Keep Alive 和属性这五个字段。PUBLISH 报文的可变报头则按顺序包含了主题名、报文标识符和属性这三个字段。
有效载荷
最后是有效载荷部分。我们可以将报文的可变报头看作是它的附加项,而有效载荷则用于实现这个报文的核心目的。
比如在 PUBLISH 报文中,Payload 用于承载具体的应用消息内容,这也是 PUBLISH 报文最核心的功能。而 PUBLISH 报文的可变报头中的 QoS、Retain 等字段,则是围绕着应用消息提供一些额外的能力。
SUBSCRIBE 报文也是如此,Payload 包含了想要订阅的主题以及对应的订阅选项,这也是 SUBSCRIBE 报文最主要的工作。