本系列文章以当前普遍使用的 MQTT 版本 3.1.1 为例,结合 wireshark 工具,详细解析 MQTT 协议里的消息流程和细节。
什么是 QoS ?
QoS (Quality of Service) 是发送者和接收者之间,对于消息传递的可靠程度的协商。
QoS 的设计是 MQTT 协议里的重点。作为专为物联网场景设计的协议,MQTT 的运行场景不仅仅是 PC,而是更广泛的窄带宽网络和低功耗设备,如果能在协议层解决传输质量的问题,将为物联网应用的开发提供极大便利。
三个 QoS 级别简介
在 MQTT 协议里,定义了三个级别的 QoS,由低到高分别是:
- 最多一次 (QoS0)
- 至少一次 (QoS1)
- 有且仅有一次 (QoS2)
QoS0 是最低级别,基本上等同于 Fire and Forget
模式,发送者发送完数据之后,不关心消息是否已经投递到了接收者那边。
QoS1 是中间级别,保证消息至少送达一次。MQTT 通过简单的 ACK 机制来保证 QoS1。
QoS2 是最高级别,保证到且仅到一次。这通过更加复杂的消息流程保证。
QoS 级别越高,流程越复杂,系统资源消耗越大。应用程序可以根据自己的网络场景和业务需求,选择合适的 QoS 级别:
比如在同一个子网内部的服务间的消息交互往往选用 QoS0;而通过互联网的实时消息通信往往选用 QoS1;QoS2 使用的场景相对少一些,能想到的如国防武器,医疗设备等应用场景。
既然 QoS 是发送者和接收者之间的质量协定,在 MQTT 协议的 Client - Broker - Client
架构里,QoS 就需要分成两部分来讨论:
- 从发送者到 Broker 之间消息传递的 QoS。这需要由发送者在 MQTT PUBLISH 消息里设置 QoS 级别。
- 从 Broker 到接收者之间消息传递的 QoS。这需要接收者在订阅 Topic 时,设置 SUBSCRIBE 消息里的 QoS 级别。
实例分析
本实例中使用的工具:
QoS 0 发布订阅
这个例子里,我们的 MQTT Client 使用 QoS0 订阅主题 t/1
, 然后发布 QoS0 消息 "hi" 到主题 t/1
:
Subscribe 的消息体里包含了 QoS 信息,这是我们前面说的第二类QoS,即 "从 Broker 到接收者之间消息传递的 QoS":
接下来是连续两个 PUBLISH 消息。
第一个是从 Client 到 Broker 的,因为 EMQ 和 MQTT Box 都是运行在本机上的,所以显示的 IP 都是 "127.0.0.1",但还是可以通过端口号区分开。62814 是 MQTT Box 的客户临时端口,而 1883 是 EMQ 的 Listening 端口:
-
注意跟 Subscribe 消息不同的是,Publish 消息的 QoS 信息作为标志位包含在MQTT 消息头里,而不是消息体中。
第二个是从 Broker 下发到 Client 的。这是因为我们前面刚刚订阅了 t/1
主题:
QoS 1 发布订阅
这个例子里,我们的 MQTT Client 使用 QoS1 订阅主题 t/2
, 然后发布 QoS1 消息 "hi" 到主题 t/2
:
Subscribe 部分跟 QoS 0 类似,只不过消息体里的 QoS 信息变成了 1。
但 Publish 消息时,多了一个 PUBACK。
从 Client 到 Broker 的 PUBLISH 消息的消息头里,可以看到它的 QoS 设置为 1 :
紧接着 Broker 回复 PUBACK,PUBLISH 消息和 PUBACK 消息通过相同的 Message Identifier 关联起来:
如果 Client 没有收到 PUBACK 的话,它将会重试,再次发送 Publish 消息。
下一个 Publish 消息是从 Broker 下发到 Client 的,紧跟着的 PUBACK 是从 Client 回复给 Broker 的。消息内容跟上面类似。
QoS 2 发布订阅
这个例子里,我们的 MQTT Client 使用 QoS2 订阅主题 t/3
, 然后发布 QoS2 消息 "hi" 到主题 t/3
:
Subscribe 部分跟 QoS 0 和 QoS 1 的类似,只不过消息体里的 QoS 信息变成了 2。
接下来的 Publish 消息流程变得复杂了一些:
当 MQTT Broker 收到 QoS2 的 PUBLISH 消息的时候,会处理这条消息并回复 PUBREC (Publish Received)。
MQTT Broker 会将消息体中的消息 ID 暂存下来。暂存消息 ID 是为了保证唯一性,如果 Broker 再次收到一条重复的 PUBLISH 消息,就可以将其忽略掉。
Client 发送完最初的 PUBLISH 消息之后,还不能马上删除这条消息的缓存,以便必要时重发。当 Client 收到 Broker 发来的 PUBREC 回复之后,知道对方已经收到了自己发出的 PUBLISH,此时就可以放心地将缓存删除了。Client 保存一下 PUBREC 里的消息ID,并发送 PUBREL (Publish Release) 消息给 Broker,以通知 Broker 删除状态缓存。
Broker 收到 PUBREL 后,知道 Client 已经不会再发送多余的 Publish 过来了,此时可以放心的删除之前已经存储的消息 ID,并回复 PUBCOMP。
当 Client 收到 PUBCOMP 时,可以删除所有缓存,因为它知道现在不需要重发 PUBREL 消息了。
另外一侧,从 Broker 下发到设备的流程与此相同。
QoS 降级
因为 QoS 分为 发送到 Broker 的 QoS
和 从 Broker 接收的 QoS
两部分,所以最终收到的 QoS 等级跟这两部分都有关系:
如果发送者发送了一个 QoS2 的消息,但是接收者订阅时使用的是QoS1,那么接收到的消息等级就是 QoS1。这个叫做 QoS 降级。
作者: liuxy@emqx.io