MQTT QoS和协议流程
文章目录
一些容易搞混的概念:
- QoS是发送者和接受者之间的协议,而不是Publisher和Subscriber之间的协定,Subscriber订阅到的消息的QoS和Publisher没有直接关系
- Subscriber订阅到的消息的QoS:Publisher发布消息到Broker的QoS为QoSA,Subscriber从Broker订阅消息的QoS为QoSB,取QoSA和QoSB这二者的最小值
QoS 0:最多分发一次
消息的分发依赖于底层网络的能力。接收者不会发送响应,发送者也不会重试。消息可能送达一次也可能根本没送达。
QoS1:至少分发一次
服务质量确保消息至少送达一次。QoS 1 的 PUBLISH 报文的可变报头中包含一个报文标识符,需要PUBACK 报文确认。QoS1的情况下,接收端收到的消息可能会重复。
对于发送者:
- 每次发送新的应用消息都必须分配一个未使用的报文标识符
- 发送的 PUBLISH 报文必须包含报文标识符且 QoS 等于 1,DUP 等于 0
- 必须将这个 PUBLISH 报文看作是 未确认的 ,直到从接收者那收到对应的 PUBACK 报文
- 一旦发送者收到 PUBACK 报文,这个报文标识符就可以重用
- 允许发送者在等待确认时使用不同的报文标识符发送后续的 PUBLISH 报文
对于接收者:
- 响应的 PUBACK 报文必须包含一个报文标识符,这个标识符来自接收到的、已经接受所有权的 PUBLISH 报文
- 发送了 PUBACK 报文之后,接收者必须将任何包含相同报文标识符的入站 PUBLISH 报文当作一 个新的消息,并忽略它的 DUP 标志的值
- 不要求接收者在发送 PUBACK 之前完整分发应用消息。原来的发送者收到 PUBACK 报文之后, 应用消息的所有权就会转移给这个接收者
为什么QoS1可能导致消息重复?
来看看以下两种情况,如下图所示:
- 左边:PUBACK传输失败了,因此发送端重发了这个报文(下图左边的2),Packet Id也就是报文标识符保持一致,DUP变为1表示消息重传。实际上,这里面只是一个QoS1的消息来回
- 右边:在完成了一次QoS1的消息交互后,报文标识符即可释放。后续的消息可以复用已释放的消息标识符,然后再发送PUBLISH报文的时候,因为某种原因失败了发生了重传(下图右边的4),这是DUP变为1。实际上,这里面是两个QoS1消息的的来回
这两种情况,Packet Id一致,且DUP均为1,同时Payload一致。对于接收方来说,无法区分是重复消息(左边步骤3)还是新消息(右边的4)。综合来看,只能让接收方将这些 PUBLISH 报文都当作全新的消息来处理。因此当我们使用 QoS 1 时,消息的重复在协议层面上是无法避免的。
QoS 2: 仅分发一次
最高等级,消息丢失和重复都不可接受。
对于发送者:
- 必须给要发送的新应用消息分配一个未使用的报文标识符
- 发送的 PUBLISH 报文必须包含报文标识符且报文的 QoS 等于 2,DUP 等于 0
- 必须将这个 PUBLISH 报文看作是 未确认的 ,直到从接收者那收到对应的 PUBREC 报文
- 收到 PUBREC 报文后必须发送一个 PUBREL 报文。PUBREL 报文必须包含与原始 PUBLISH 报文 相同的报文标识符
- 必须将这个 PUBREL 报文看作是 未确认的 ,直到从接收者那收到对应的 PUBCOMP 报文
- 一旦发送了对应的 PUBREL 报文就不能重发这个 PUBLISH 报文
- 一旦发送者收到 PUBCOMP 报文,这个报文标识符就可以重用
对于接收者:
- 响应的 PUBREC 报文必须包含报文标识符,这个标识符来自接收到的、已经接受所有权的PUBLISH 报文
- 在收到对应的 PUBREL 报文之前,接收者必须发送 PUBREC 报文确认任何后续的具有相同标识符的 PUBLISH 报文。 在这种情况下,它不能重复分发消息给任何后续的接收者
- 响应 PUBREL 报文的 PUBCOMP 报文必须包含与 PUBREL 报文相同的标识符
- 发送 PUBCOMP 报文之后,接收者必须将包含相同报文标识符的任何后续 PUBLISH 报文当作一个新的发布
下图中,在完成了步骤3后,说明接收者已经收到PUBLISHB报文,发送者就不再需要重复发送这个PUBLISH报文,因此就可以删除掉本地缓存的PUBLISH报文。
为什么QoS2可以避免重复消息?
首先QoS2和QoS1类似,当发送者发送了PUBLISH报文以后,是需要接收者的PUBREC报文来告诉发送者PUBLISH已收到。
不同的是,对于QoS1来说,在此之后相同的报文标识符又变得可用,这样导致可能的消息重复。对于QoS2而言,释放报文标识符也是需要通过一组PUBREL/PUBCOM使得发送者和接收者双方达成一致以后才能释放报文标识符。发送方只有在收到 PUBREC 报文之前可以重传 PUBLISH 报文。一旦收到 PUBREC 报文并发出 PUBREL 报文,发送方就进入了 Packet ID 释放流程,不可以再使用当前 Packet ID 重传 PUBLISH 报文。同时,在收到对端回复的 PUBCOMP 报文确认双方都完成 Packet ID 释放之前,也不可以使用当前 Packet ID 发送新的消息。
QoS和Session
客户端的会话状态包括:
已经发送给服务端,但是还没有完成确认的 QoS 1 和 QoS 2 级别的消息
已从服务端接收,但是还没有完成确认的 QoS 2 级别的消息。
服务端的会话状态包括:
- 会话是否存在(即使会话状态的其它部分都是空)
- 客户端的订阅信息
- 已经发送给客户端,但是还没有完成确认的 QoS 1 和 QoS 2 级别的消息
- 即将传输给客户端的 QoS 1 和 QoS 2 级别的消息
- 已从客户端接收,但是还没有完成确认的 QoS 2 级别的消息
- 可选,准备发送给客户端的 QoS 0 级别的消息
保留消息不是服务端会话状态的一部分,会话终止时不能删除保留消息。
QoS等级的选择
在以下情况下你可以选择 QoS0:
- Client 和 Broker 之间的网络连接非常稳定,例如一个通过有线网络连接到 Broker 的测试用 Client
- 可以接受丢失部分消息,比如你有一个传感器以非常短的间隔发布状态数据,所以丢一些也可以接受
- 你不需要离线消息
在以下情况下你应该选择 QoS1:
- 你需要接收所有的消息,而且你的应用可以接受并处理重复的消息;
- 你无法接受 QoS2 带来的额外开销,QoS1 发送消息的速度比 QoS2 快很多
在以下情况下你应该选择 QoS2:
- 你的应用必须接收到所有的消息,而且你的应用在重复的消息下无法正常工作,同时你也能接受 QoS2 带来的额外开销