--- title: protocol-app-mqtt-Qos-Session date: 2020-02-03 14:16:51 categories: tags: - mqtt - protocol ---
背景
QoS 等级 与 通信的流程有关,直接影响了整个通信。而且篇幅比较长,所以我觉得应该单独拎出来讲一下。
概念
QoS 代表了 服务质量等级。 设置上,由2 位 的二进制控制,且值不允许为 3(0x11)。
QoS值 | Bit 2 | Bit 1 | 描述 |
0 | 0 | 0 | 最多分发一次 |
1 | 0 | 1 | 至少分发一次 |
2 | 1 | 0 | 只分发一次 |
- | 1 | 1 | 保留位 |
要注意的是,QoS 是 Sender
和 Receiver
之间达成的协议,不是 Publisher
和 Subscriber
之间达成的协议。
也就是说
Publisher
发布一条 QoS1 的消息,只能保证Broker
能至少收到一次这个消息;至于对应的Subscriber
能否至少收到一次这个消息,还要取决于Subscriber
在Subscribe
的时候和Broker
协商的 QoS 等级。这里又牵扯出一个概念:"QoS 降级":在 MQTT 协议中,从 Broker 到 Subscriber 这段消息传递的实际 QoS 等于 "Publisher 发布消息时指定的 QoS 等级和 Subscriber 在订阅时与 Broker 协商的 QoS 等级,这两个 QoS 等级中的最小那一个。"
QoS 0 的通信时序图
此时,整个过程中的 Sender
不关心 Receiver
是否收到消息,它"尽力"发完消息,至于是否有人收到,它不在乎。
%% 时序图 sequenceDiagram title : QoS 0:At most one(Fire and forget) participant 发布者 participant 服务器 participant 订阅者 发布者 ->> + 服务器: PUBLISH (QoS0,Msg-A) 服务器 ->> 订阅者: PUBLISH(QoS0,Msg-A) 发布者 ->> 发布者: Delete Msg-A
QoS1 的通信时序图
此时,Sender
发送的一条消息,Receiver
至少能收到一次,也就是说 Sender
向 Receiver
发送消息,如果发送失败,会继续重试,直到 Receiver
收到消息为止,但是因为重传的原因,Receiver
有可能会收到重复的消息;
%% 时序图 sequenceDiagram participant 发布者 participant 服务器 participant 订阅者 title : QoS 1:At least one 发布者->>发布者: Store (Msg-A) 发布者 ->> 服务器: PUBLISH (QoS1,Msg-A) 服务器->>服务器: Store (Msg-A) 服务器 ->> 订阅者: PUBLISH (QoS1,Msg-A) 服务器 -->> 发布者: PUBACK (QoS1) 发布者->>发布者: Delete (Msg-A) 订阅者 -->> 服务器: PUBACK (QoS1,Msg-A) 服务器->>服务器: Delete (Msg-A)
1)Sender 向 Receiver 发送一个带有消息数据的 PUBLISH 包, 并在本地保存这个 PUBLISH 包。
2)Receiver 收到 PUBLISH 包以后,向 Sender 发送一个 PUBACK 数据包,PUBACK 数据包没有消息体(Payload),在可变头中(Variable header)中有一个包标识(Packet Identifier),和它收到的 PUBLISH 包中的 Packet Identifier 一致。
3)Sender 收到 PUBACK 之后,根据 PUBACK 包中的 Packet Identifier 找到本地保存的 PUBLISH 包,然后丢弃掉,一次消息的发送完成。
4)如果 Sender 在一段时间内没有收到 PUBLISH 包对应的 PUBACK,它将该 PUBLISH 包的 DUP 标识设为 1(代表是重新发送的 PUBLISH 包),然后重新发送该 PUBLISH 包。重复这个流程,直到收到 PUBACK,然后执行第 3 步。
QoS 2 的通信时序图
QoS2 不仅要确保 Receiver 能收到 Sender 发送的消息,还要保证消息不重复。它的重传和应答机制就要复杂一些,同时开销也是最大的。
Sender 发送的一条消息,Receiver 确保能收到而且只收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,同时保证 Receiver 不会因为消息重传而收到重复的消息。
%% 时序图 sequenceDiagram participant 发布者 participant 服务器 participant 订阅者 title : QoS 2:Exactly one 发布者->>发布者: Store (Msg-A) 发布者 ->> 服务器: PUBLISH (QoS2,Msg-A,DUP=0) 服务器->>服务器: Store (Msg-A) 服务器 -->> 发布者: PUBREC (QoS2,Msg-A) 发布者 ->> 服务器: PUBREL (QoS2,Msg-A) 服务器 ->> 订阅者 : PUBLISH (QoS2,Msg-A,DUP=0) 服务器 -->> 发布者: PUBCOMP (QoS2,Msg-A) 发布者->>发布者: Delete (Msg-A) 订阅者->>订阅者: Store (Msg-A) 订阅者 -->> 服务器: PUBREC (QoS2,Msg-A) 服务器 ->> 订阅者: PUBREL (QoS2,Msg-A) 订阅者->>订阅者: Notify (Msg-A) 订阅者 -->> 服务器: PUBCOMP (QoS2,Msg-A) 服务器->>服务器: Delete (Msg-A) 订阅者->>订阅者: Delete (Msg-A)
QoS 使用 2 套请求/应答流程(一个 4 段的握手)来确保 Receiver 收到来自 Sender 的消息,且不重复:
1)Sender 发送 QoS 为 2 的 PUBLISH 数据包,数据包 Packet Identifier 为 P,并在本地保存该 PUBLISH 包;
2)Receiver 收到 PUBLISH 数据包以后,在本地保存 PUBLISH 包的 Packet Identifier P,并回复 Sender 一个 PUBREC 数据包,PUBREC 数据包可变头中的 Packet Identifier 为 P,没有消息体(Payload);
3)当 Sender 收到 PUBREC,它就可以安全地丢弃掉初始的 Packet Identifier 为 P 的 PUBLISH 数据包,同时保存该 PUBREC 数据包,同时回复 Receiver 一个 PUBREL 数据包,PUBREL 数据包可变头中的 Packet Identifier 为 P,没有消息体;如果 Sender 在一定时间内没有收到 PUBREC,它会把 PUBLISH 包的 DUP 标识设为 1,重新发送该 PUBLISH 数据包(Payload);
4)当 Receiver 收到 PUBREL 数据包,它可以丢弃掉保存的 PUBLISH 包的 Packet Identifier P,并回复 Sender 一个 PUBCOMP 数据包,PUBCOMP 数据包可变头中的 Packet Identifier 为 P,没有消息体(Payload);
5)当 Sender 收到 PUBCOMP 包,那么它认为数据包传输已完成,它会丢弃掉对应的 PUBREC 包。如果 Sender 在一定时间内没有收到 PUBCOMP 包,它会重新发送 PUBREL 数据包。
我们可以看到在 QoS2 中,完成一次消息的传递,Sender 和 Reciever 之间至少要发送四个数据包,QoS2 是最安全也是最慢的一种 QoS 等级了。
QoS 和会话(Session)
客户端的会话状态包括:
- 已经发送给服务端,但是还没有完成确认的QoS 1和QoS 2级别的消息
- 已从服务端接收,但是还没有完成确认的QoS 2级别的消息。
服务端的会话状态包括:
- 会话是否存在,即使会话状态的其它部分都是空。
- 客户端的订阅信息。
- 已经发送给客户端,但是还没有完成确认的QoS 1和QoS 2级别的消息。
- 即将传输给客户端的QoS 1和QoS 2级别的消息。
- 已从客户端接收,但是还没有完成确认的QoS 2级别的消息。
- 可选,准备发送给客户端的QoS 0级别的消息。
保留消息不是服务端会话状态的一部分,会话终止时不能删除保留消息。
如果 Client 想接收离线消息,必须使用持久化的会话(CONNECT报文中可变头(byte8[1])Clean Session = 0)连接到 Broker,这样 Broker 才会存储 Client 在离线期间没有确认接收的 QoS 大于 1 的消息。
QoS 等级的选择
在以下情况下你可以选择 QoS0:
- Client 和 Broker 之间的网络连接非常稳定,例如一个通过有线网络连接到 Broker 的测试用 Client;
- 可以接受丢失部分消息,比如你有一个传感器以非常短的间隔发布状态数据,所以丢一些也可以接受;
- 你不需要离线消息。
在以下情况下你应该选择 QoS1:
- 你需要接收所有的消息,而且你的应用可以接受并处理重复的消息;
- 你无法接受 QoS2 带来的额外开销,QoS1 发送消息的速度比 QoS2 快很多。
在以下情况下你应该选择 QoS2:
- 你的应用必须接收到所有的消息,而且你的应用在重复的消息下无法正常工作,同时你也能接受 QoS2 带来的额外开销。
实际上,QoS1 是应用最广泛的 QoS 等级,QoS1 发送消息的速度很快,而且能够保证消息的可靠性。虽然使用 QoS1 可能会收到重复的消息,但是在应用程序里面处理重复消息,通常并不是件难事。