版权信息
warning
本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本篇参考 EMQ的官方教学文档——MQTT 教程 2026:从入门到精通 | EMQ
1. MQTT概述
1.1. 什么是MQTT?
MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布-订阅模式的消息传输协议,适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通信。
1.2. MQTT的部分特点
-
安全通信
MQTT 提供传输层安全(TLS)和安全套接层(SSL)加密功能。此外,MQTT 还通过用户名/密码凭证或客户端证书提供身份验证和授权机制,以保护网络及其资源的访问。
-
连续、有状态的会话
MQTT 提供了客户端与 Broker 之间保持有状态会话的能力,这使得系统即使在断开连接后也能记住订阅和未传递的消息。此外,客户端还可以在建立连接时指定一个保活间隔,这会促使 Broker 定期检查连接状态。如果连接中断,Broker 会储存未传递的消息(根据 QoS 级别确定),并在客户端重新连接时尝试传递它们。
-
发送者与订阅者解耦
通过采用发布-订阅模式,MQTT 实现了发送者和接收者的解耦,从而有效地减少了网络流量和资源使用。
2. MQTT的工作原理
MQTT 是基于发布-订阅模式的通信协议,由 MQTT 客户端通过主题(Topic)发布或订阅消息,通过 MQTT Broker 集中管理消息路由,并依据预设的服务质量等级(QoS)确保端到端消息传递可靠性。
2.1. MQTT客户端
任何运行MQTT 客户端库的应用或设备都是 MQTT 客户端。
2.2. MQTT Server/Broker
MQTT Server 是负责处理客户端请求的关键组件,包括建立连接、断开连接、订阅和取消订阅等操作,同时还负责消息的转发。
2.3. 发布-订阅模式
不同于客户端-服务器模式,它将发送消息的客户端(发布者)和接收消息的客户端(订阅者)进行了解耦。发布者和订阅者之间没有直接连接,而是通过 MQTT Server 来负责消息的路由和分发。
2.4. 主题(Topic)
MQTT 协议根据主题来转发消息。
2.5. QoS
MQTT 提供了三种服务质量(QoS),在不同网络环境下保证消息的可靠性。
- QoS 0:消息最多传送一次。如果当前客户端不可用,它将丢失这条消息。
- QoS 1:消息至少传送一次。
- QoS 2:消息只传送一次。
3. MQTT的工作流程
-
客户端使用 TCP/IP 协议与 Broker 建立连接,可以选择使用 TLS/SSL 加密来实现安全通信。客户端提供认证信息,并指定会话类型(Clean Session 或 Persistent Session)。
-
客户端既可以向特定主题发布消息,也可以订阅主题以接收消息。当客户端发布消息时,它会将消息发送给 MQTT Broker;而当客户端订阅消息时,它会接收来自Broker转发的与订阅主题相关的消息。
-
MQTT Broker 接收发布的消息,并将这些消息转发给订阅了对应主题的客户端。它根据 QoS 等级确保消息可靠传递,并根据会话类型为断开连接的客户端存储消息。
4. MQTT的特性
4.1. 主题(topic)与通配符
更多内容请参见——MQTT 主题与通配符(Topics & Wildcards)入门手册 | EMQ
4.1.1. 主题
MQTT 主题本质上是一个 UTF-8 编码的字符串,是 MQTT 协议进行消息路由的基础。MQTT 主题类似 URL 路径,使用斜杠 / 进行分层:
chat/room/1
sensor/10/temperature
sensor/+/temperature
sensor/#
4.1.2. 特殊主题
-
以
$SYS/开头的主题为系统主题。系统主题主要用于获取MQTT 服务器自身运行状态、消息统计、客户端上下线事件等数据。目前,MQTT 协议暂未明确规定$SYS/主题标准,但大多数 MQTT 服务器都遵循该标准建议。 -
以
$share开头的共享订阅主题。这是MQTT5.0引入的新特性,请参见——
4.1.3. 通配符
MQTT 主题通配符包含单层通配符 + 及多层通配符 #,主要用于客户端一次订阅多个主题。
-
加号 (“+” U+002B) 是用于单个层级匹配的通配符。
+ 有效 sensor/+ 有效 sensor/+/temperature 有效 sensor+ 无效(没有占据整个层级)如果客户端订阅了主题
sensor/+/temperature,将会收到以下主题的消息:sensor/1/temperature sensor/2/temperature ... sensor/n/temperature而不会匹配:
sensor/temperature sensor/bedroom/1/temperature -
井字符号(“#” U+0023)是递归的匹配。它必须占据整个层级并且必须是主题的最后一个字符。
# 有效,匹配所有主题 sensor/# 有效 sensor/bedroom# 无效(没有占据整个层级) sensor/#/temperature 无效(不是主题最后一个字符)如果客户端订阅主题
senser/#,它将会收到以下主题的消息:sensor sensor/temperature sensor/1/temperature
4.2. QoS
更多内容请参见——MQTT QoS 0、1、2 解析:快速入门指南 | EMQ
引入QoS解决在不同场景下对可靠消息传递的要求。比如有些不需要那么严格但要求效率,有些则需保证数据的可靠性。
MQTT 中的 QoS 指的是发布者与订阅者之间消息传递的保证级别。它提供三个服务级别:
- QoS 0 – 最多交付一次
- QoS 1 – 至少交付一次
- QoS 2 – 只交付一次
使用 QoS 0 可能丢失消息,使用 QoS 1 可以保证收到消息,但消息可能重复,使用 QoS 2 可以保证消息既不丢失也不重复。QoS 等级从低到高,不仅意味着消息可靠性的提升,也意味着传输复杂程度的提升。
大部分情况下 Broker 向订阅者转发消息时都会维持原始的 QoS 不变。不过也有一些例外的情况,根据订阅者的订阅要求,消息的 QoS 等级可能会在转发的时候发生降级。
例如,订阅者在订阅时要求 Broker 可以向其转发的消息的最大 QoS 等级为 QoS 1,那么后续所有 QoS 2 消息都会降级至 QoS 1 转发给此订阅者,而所有 QoS 0 和 QoS 1 消息则会保持原始的 QoS 等级转发。
4.2.1. QoS 0 - 最多传一次
QoS 0 消息即发即弃,不需要等待确认,不需要存储和重传。类比UDP。所以在网络不稳定的情况下会出现丢包的问题。
-
适用场景
QoS 0 的缺点是可能会丢失消息,不过优点是投递的效率较高。
所以我们通常选择使用 QoS 0 传输一些高频且不那么重要的数据,比如传感器数据,周期性更新,即使遗漏几个周期的数据也可以接受。
4.2.2. QoS 1 - 至少传一次
QoS 1 加入了应答与重传机制,发送方只有在收到接收方的 PUBACK 报文以后,才能认为消息投递成功,在此之前,发送方需要存储该 PUBLISH 报文(发布报文)以便下次重传。
重传时报文中的 DUP 标志会被设置为 1,已表示这是一个重传的报文。但是接收方并不能因此判断曾经是否接收过这个消息。比如下面两种情况:
- 接收方收到了报文,但发送方由于没有收到 PUBACK 报文而重传了 PUBLISH 报文。
- 接收方没有接收到报文。
由于我们无法区分这两种情况,所以只能让接收方将这些 PUBLISH 报文都当作全新的消息来处理。因此当我们使用 QoS 1 时,消息的重复在协议层面上是无法避免的。
-
适用场景
QoS 1 可以保证消息到达,所以适合传输一些较为重要的数据,比如下达关键指令、更新重要的有实时性要求的状态等。
但因为 QoS 1 还可能会导致消息重复,所以当我们选择使用 QoS 1 时,还需要在业务层面能够处理消息的重复,或者能够允许消息的重复。
如何为QoS1去重?一个比较常用且简单的方法是,在每个 PUBLISH 报文的 Payload(实际的数据内容) 中都带上一个时间戳或者一个单调递增的计数,这样上层业务就可以根据当前收到消息中的时间戳或计数是否大于自己上一次接收的消息中的时间戳或计数来判断这是否是一个新消息。
4.2.3. QoS 2 - 只传一次
QoS 2 解决了 QoS 0、1 消息可能丢失或者重复的问题,但相应地,它也带来了最复杂的交互流程和最高的开销。 每一次的 QoS 2 消息投递,都要求发送方与接收方进行至少两次请求/响应流程。
具体流程可参见前面的链接。这里篇幅原因不详说 其实是自己也没明白
-
适用场景
QoS 2 既可以保证消息到达,也可以保证消息不会重复,但传输成本最高。如果我们不愿意自行实现去重方案,并且能够接受 QoS 2 带来的额外开销,那么 QoS 2 将是一个合适的选择。
4.3. 保留消息(Retained)
更多内容请参见——MQTT 保留消息是什么?如何使用? | EMQ
4.3.1. 什么是?
发布者发布消息时,如果 Retained 标记被设置为 true,则该消息即是 MQTT 中的保留消息(Retained Message)。MQTT 服务器会为每个主题存储最新一条保留消息,以方便消息发布后才上线的客户端在订阅主题时仍可以接收到该消息。
4.3.2. 何时用?
发布-订阅模式,订阅者无法主动向发布者请求消息。订阅者何时收到消息完全依赖于发布者何时发布消息,这在某些场景中就产生了不便。
借助保留消息,新的订阅者能够立即获取最近的状态,而不需要等待无法预期的时间,例如:
- 智能家居设备的状态只有在变更时才会上报,但是控制端需要在上线后就能获取到设备的状态;
- 传感器上报数据的间隔太长,但是订阅者需要在订阅后立即获取到最新的数据;
- 传感器的版本号、序列号等不会经常变更的属性,可在上线后发布一条保留消息告知后续的所有订阅者。
需要注意的是,在保留消息发布前订阅主题,将不会收到保留消息。需要待保留消息发布后,重新订阅该主题,才会收到保留消息。 可通过消息中的保留标志位判断是否是保留消息。
4.3.3. 保留多久?怎么删?
保留消息虽然存储在服务端中,但它并不属于会话的一部分。因此即使会话已经结束,保留消息也不一定被删除。删除保留消息有以下几种方式:
- 客户端往某个主题发送一个 Payload 为空的保留消息,服务端就会删除这个主题下的保留消息;
- 在 MQTT 服务器上删除,比如 EMQX MQTT 服务器提供了在 Dashboard 上删除保留消息的功能;
4.3.4. MQTT 5.0 对保留消息的改进
MQTT 5.0 新增了消息过期间隔属性,发布时可使用该属性设置消息的过期时间,不管消息是否为保留消息,都将会在过期时间后自动被删除。具体介绍请参见本节头部链接。
4.4. 遗嘱消息(Will)
更多内容请参见——遗嘱消息(Will Message)介绍与示例 | MQTT 5.0 特性详解 | EMQ
4.4.1. 什么是?
在 MQTT 中,客户端可以在连接时在服务端中注册一个遗嘱消息,与普通消息类似,我们可以设置遗嘱消息的主题、有效载荷等等。当该客户端意外断开连接,服务端就会向其他订阅了相应主题的客户端发送此遗嘱消息。这些接收者也因此可以及时地采取行动,例如向用户发送通知、切换备用设备等等。
借助遗嘱消息,订阅者可以感知到发布者是否保持活跃,根据不同情况做出不同反应。
客户端正常下线的情况是发送一个 Reason Code 为 0x00 的 DISCONNECT 报文然后关闭网络连接,服务端不会发布遗嘱消息。只要连接在服务端没有收到 Reason Code 为 0x00 的 DISCONNECT 报文的情况下关闭,那么服务端都需要发送遗嘱消息。
遗嘱消息总是在客户端“死亡”后被发布,在某种意义上,它也是客户端发出的最后一个消息。
4.4.2. 使用技巧
-
与保留消息一起使用
服务端一旦发布了遗嘱消息,就会将它从会话中删除。如果关心此遗嘱消息的客户端不在线,那么它就错过了这条遗嘱消息。
为了避免这种情况,我们可以将遗嘱消息设置为保留消息,这样遗嘱消息在被发布后,还会以保留消息的形式存储在服务端中,客户端可以在任何时候获取这条遗嘱消息。
如果更进一步,我们还可以实现对指定客户端的状态监控。
让客户端
myclient在每次连接时都指定一个主题为myclient/status,有效载荷为offline并且设置了 Will Retain 标志的遗嘱消息。每当连接成功,就向主题myclient/status发布一个有效载荷为online的保留消息。这样,我们就可以随时订阅主题myclient/status,来获取客户端myclient的最新状态。
4.4.3. MQTT 5.0 对遗嘱消息的改进
很多时候,网络连接的中断是短暂的,所以客户端往往能够重新连接并继续之前的会话。这导致遗嘱消息可能被频繁地且无意义地发送。
所以 MQTT 5.0 专门为遗嘱消息增加了一个 Will Delay Interval 属性,这个属性决定了服务端将在网络连接关闭后延迟多久发布遗嘱消息,并以秒为单位。
具体介绍请参见本节头部链接。
4.5. 持久会话与Clean Session
更多内容请参见——MQTT 持久会话与 Clean Session 详解 | EMQ
4.5.1. 持久会话
MQTT 客户端与服务器的连接可能随时会因为网络波动及资源限制而异常断开。为了解决网络连接断开对通信造成的影响,MQTT 协议提供了持久会话功能。
MQTT 客户端在发起到服务器的连接时,可以设置是否创建一个持久会话。持久会话会保存一些重要的数据,以使会话能在多个网络连接中继续。持久会话主要有以下三个作用:
- 避免因网络中断导致需要反复订阅带来的额外开销。
- 避免错过离线期间的消息。
- 确保 QoS 1 和 QoS 2 的消息质量保证不被网络中断影响。(QoS 0 由于其特性并不在此列)
4.5.2. 持久会话能存储的数据
客户端中存储的会话数据:
- 已发送给服务端,但是还没有完成确认的 QoS 1 与 QoS 2 消息。
- 从服务端收到的,但是还没有完成确认的 QoS 2 消息。
服务端中存储的会话数据:
- 会话是否存在,即使会话状态其余部分为空。
- 已发送给客户端,但是还没有完成确认的 QoS 1 与 QoS 2 消息。
- 等待传输给客户端的 QoS 0 消息(可选),QoS 1 与 QoS 2 消息。
- 从客户端收到的,但是还没有完成确认的 QoS 2 消息,遗嘱消息和遗嘱延时间隔。
4.5.3. 使用Clean Session开启持久会话
Clean Session 是用来控制会话状态生命周期的标志位,为 true 时表示创建一个新的会话,在客户端断开连接时,会话将自动销毁。为 false 时表示创建一个持久会话,在客户端断开连接后会话仍然保持,直到会话超时注销。
注意: 持久会话能被恢复的前提是客户端使用固定的 Client ID 再次连接,如果 Client ID 是动态的,那么连接成功后将会创建一个新的持久会话。
4.5.4. 会话会保留多久?
MQTT 3.1.1 没有规定持久会话应该在什么时候过期,如果仅从协议层面理解的话,这个持久会话应该永久存在。但在实际场景中这并不现实,因为它会非常占用服务端的资源,所以服务端通常不会完全遵循协议来实现,而是向用户提供一个全局配置来限制会话的过期时间。
当客户端确定不再需要会话时,可使用 Clean Session 为 true 进行重连,重连成功后再断开连接。如果是 MQTT 5.0 则可在断开连接时直接设置 Session Expiry Interval 为 0,表示连接断开后会话即失效。(见后文)
4.5.5. MQTT 5.0 对会话的改进
MQTT 5.0 提供了 Clean Start 和 Session Expiry Interval 这两个连接字段来控制会话的生命周期。以避免永久保留持久会话。
-
Clean Start
- 为 0 : 如果服务端存在与客户端连接时指定的 Client ID 关联的会话,那么它必须使用这个会话来恢复通信。如若不存在关联的对话,服务端创建一个全新对话并让客户端以此会话连接。
- 为 1 : 客户端和服务端必须丢弃任何已存在的会话,并开始一个全新的会话。
-
Session Expiry Interval
- 没有指定此属性或者设置为 0,表示会话将在网络连接断开时立即结束。
- 设置为一个大于 0 的值,则表示会话将在网络连接断开的多少秒之后过期。
- 设置为 0xFFFFFFFF,即 Session Expiry Interval 属性能够设置的最大值时,表示会话永不过期。
5. MQTT 5.0 的新特性
5.1. 请求/响应(Request/Response)
可以实现订阅端对发布端的响应(类似函数的return)。解决了发布端无法知道对端执行结果/是否收到包的问题
请参见——请求 / 响应 (Request / Response) 介绍与示例 | MQTT 5.0 特性详解 | EMQ
5.2. 用户属性(User Properties)
一种自定义属性,允许用户向 MQTT 消息添加自己的元数据,传输额外的自定义信息以扩充更多应用场景。让用户可以扩展标准协议。
请参见——用户属性 - MQTT 5.0 新特性 | EMQ
5.3. 主题别名(Topic alias)
将主题长度较长且常用的主题名缩减为一个双字节整数来降低发布消息时的带宽消耗。
请参见——主题别名 - MQTT 5.0 新特性 | EMQ
5.4. Payload Format Indicator 和 Content Type
Payload Format Indicator用于指定报文有效载荷的格式。如果 Payload Format Indicator 的值为 0 或者没有指定这个属性,表示当前有效载荷是未指定的字节流;而如果这个属性的值为 1,则表示当前有效载荷是 UTF-8 编码的字符数据。
Content Type 的值是一个 UTF-8 编码的字符串,用来描述应用消息的内容,这可以帮助接收端了解如何解析应用消息的有效载荷。例如,消息的内容是一个 JSON 对象,那么 Content Type 可以被设置为 “json”。这个字符串的具体内容完全由发送端和接收端决定,服务端不会验证这个属性,只负责将这个属性原封不动地转发给订阅者。
请参见——Payload Format Indicator 和 Content Type 介绍与示例 | MQTT 5.0 特性详解 | EMQ
5.5. 共享订阅(Shared Sub)
当多个客户端共享一个订阅时,服务端会把消息一个一个送给订阅者,而不是一次性同时送给订阅者。就像串行和并发的区别。
请参见——共享订阅介绍与示例 | MQTT 5.0 特性详解 | EMQ
5.6. 更多
不想写了。。。
请参见——MQTT 教程 2026:从入门到精通 | EMQ