本文基于标准MQTT讨论,不适合其他对MQTT机制做了修改的非标准MQTT协议。
MQTT设计了一套保证消息稳定传输的机制,包括消息应答、存储和重传。在这套机制下,提供了三种不同层次QoS(Quality of Service):
- QoS0,发送就不管了,最多一次;
- QoS1,发送之后依赖MQTT规范,是否启动重传消息,所以至少一次;
- QoS2,发送之后依赖MQTT消息机制,确保只有一次。
QoS 是消息的发送方(Sender)和接受方(Receiver)之间达成的一个协议:(MQTT不是端到端的通信)
- QoS0 代表,Sender 发送的一条消息,Receiver 最多能收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,也就算了;这是完全依赖TCP重传机制,如果网络不好,TCP的重传也不是100%可靠,加上MQTT是Publisher 发出去的消息是依赖代理服务器完成转发,所以消息最多一次。
- QoS1 代表,Sender 发送的一条消息,Receiver 至少能收到一次,也就是说 Sender 向 Receiver 发送消息,如果发送之后没有收到对应的PUBACK,就会继续重试,直到发送者Sender 接收到 Receiver 发送的 PUBACK 为止,因为重传的原因,Receiver 有可能会收到重复的消息;
- QoS2 代表,Sender 发送的一条消息,Receiver 确保能收到而且只收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,同时保证 Receiver 不会因为消息重传而收到重复的消息。(个人理解这一点有点像TCP三次握手的交互过程)
下面讨论Qos不降级的情况,即订阅者与发布者的Qos等级相同。
Qos 0的交互流程:
- 消息的分发依赖于底层网络的能力。接收者不会发送响应,发送者也不会重试。消息可能送达一次也可能根本没送达。消息从Publisher发送给代理服务broker,或者broker发送给Subscriher,都可能会丢失。
注意:对于QoS 0的消息, DUP标志必须设置为0
Qos 1的交互流程:
只有当QoS等级是1或2时, 报文标识符( Packet Identifier) 字段才能出现在PUBLISH报文中。下面是MQTT Qos的补充说明:
[MQTT-4.3.2-1] 对于QoS 1的分发协议, 发送者
- 每次发送新的应用消息都必须分配一个未使用的报文标识符。
- MUST send a PUBLISH Packet containing this Packet Identifier with QoS=1,DUP=0。
- 发送的PUBLISH报文必须包含报文标识符且QoS等于1,DUP等于0。
- 必须将这个PUBLISH报文看作是 未确认的 , 直到从接收者那收到对应的PUBACK报文。 4.4节有一个关于未确认消息的讨论。
[MQTT-4.3.2-2] 对于QoS 1的分发协议, 接收者
- 响应的PUBACK报文必须包含一个报文标识符,这个标识符来自接收到的、已经接受所有权的PUBLISH报文。
- 发送了PUBACK报文之后,接收者必须将任何包含相同报文标识符的入站PUBLISH报文,当作一个新的消息, 并忽略它的DUP标志的值。
- Sender 向 Receiver 发送一个带有消息数据的 PUBLISH 包, 并在本地保存这个 PUBLISH 包。
- Receiver 收到 PUBLISH 包以后,向 Sender 发送一个 PUBACK 数据包,PUBACK 数据包没有消息体(Payload),在可变头中(Variable header)中有一个包标识(Packet Identifier),和它收到的 PUBLISH 包中的报文标识符(Packet Identifier) 一致。
- Sender 收到 PUBACK 之后,根据 PUBACK 包中的 Packet Identifier 找到本地保存的 PUBLISH 包,然后丢弃掉,一次消息的发送完成。
- 如果 Sender 在一段时间内没有收到 PUBLISH 包对应的 PUBACK,它将该 PUBLISH 包的 DUP 标识设为 1(代表是重新发送的 PUBLISH 包),然后重新发送该 PUBLISH 包。重复这个流程,直到收到 PUBACK,然后执行第 3 步。
注意:Qos 1代理服务器是不会进行去重的,只要发布者或者代理服务器没有收到PUBACK,就认为主题消息没有发送成功进入重发,代理服务器或者订阅者,不会根据dup的值进行去重。
换句话说,代理服务器(broker)或者订阅者(Subscriber)在发送PUBACK报文时,(Publisher 者)发布消息主题的程序或者代理服务器(broker)的程序,已经对PUBACK报文做了判断,那么还是会重发该主题消息,并且dup标志位会+1,这样订阅者或者代理服务器就会收到多份重复的消息。并且不会去重!
Qos 2的交互流程:
只有当QoS等级是1或2时, 报文标识符( Packet Identifier) 字段才能出现在PUBLISH报文中,另外QoS 2在消息头有Message ID。下面是补充说明
[MQTT-4.3.3-1] 对于QoS 2的分发协议, 发送者
1、必须给要发送的新应用消息分配一个未使用的报文标识符。
- MUST send a PUBLISH packet containing this Packet Identifier with QoS=2,DUP=0。
2、发送的PUBLISH报文必须包含报文标识符且报文的QoS等于2,DUP等于0。
- 必须将这个PUBLISH报文看作是 未确认的 , 直到从接收者那收到对应的PUBREC报文。 4.4节有一个关于未确认消息的讨论。
- 收到PUBREC报文后必须发送一个PUBREL报文。 PUBREL报文必须包含与原始PUBLISH报文相同的报文标识符。
- 必须将这个PUBREL报文看作是 未确认的 , 直到从接收者那收到对应的PUBCOMP报文。
- 一旦发送了对应的PUBREL报文就不能重发这个PUBLISH报文。
[MQTT-4.3.3-2] 对于QoS 2的分发协议, 接收者
- 响应的PUBREC报文必须包含报文标识符, 这个标识符来自接收到的、 已经接受所有权的PUBLISH报文。
- 在收到对应的PUBREL报文之前, 接收者必须发送PUBREC报文确认任何后续的具有相同标识符的PUBLISH报文。 在这种情况下, 它不能重复分发消息给任何后续的接收者。
- 响应PUBREL报文的PUBCOMP报文必须包含与PUBREL报文相同的标识符。
发送PUBCOMP报文之后, 接收者必须将包含相同报文标识符的任何后续PUBLISH报文当作一个新的发布。
- Sender 发送 QoS 为 2 的 PUBLISH 数据包,数据包包含报文标识符:Packet Identifier ,并在本地保存该 PUBLISH 包;
- Receiver (代理服务器或者订阅者)收到 PUBLISH 数据包以后,在本地保存 PUBLISH 包,并回复 Sender 一个 PUBREC 数据包,PUBREC 数据包可变头中的 报文标识符(Packet Identifier) 与 Sender 发送的报文标识符(Packet Identifier)一致,但是没有消息体(Payload);
- 当 Sender 收到 PUBREC,它就可以安全地丢弃掉初始发送的PUBLISH包,同时保存该 PUBREC 数据包,同时回复 Receiver 一个 PUBREL 数据包,PUBREL 数据包可变头中的报文标识符(Packet Identifier)保持不变,同样没有消息体(Payload);
- 如果 Sender 在一定时间内没有收到 PUBREC,它会把 PUBLISH 包的重发标志位 DUP 标识设为 1,重新发送该 PUBLISH 数据包(Payload);(在第三步和第四步之间是可能存在消息重传的)
- 当 Receiver 收到 PUBREL 数据包,它可以丢弃掉保存的 PUBLISH 包,并回复 Sender 一个 PUBCOMP 数据包,PUBCOMP 数据包可变头中的报文标识符(Packet Identifier)保持不变,没有消息体(Payload);
- 当 Sender 收到 PUBCOMP 包,那么它认为数据包传输已完成,它会丢弃掉对应的 PUBREC 包。如果 Sender 在一定时间内没有收到 PUBCOMP 包,它会重新发送 PUBREL 数据包。
注意:
- 1.Qos 2代理服务器是不会进行去重的,只要发布者或者代理服务器,没有收到PUBREC,就认为主题消息没有发送成功进入重发,代理服务器或者订阅者,不会根据dup的值进行去重。这一步会有去重,保证收到主题消息的唯一性。
- 发送者或者代理服务器,收到PUBREC,就认为主题消息已经发送出去,不会进行重复发送;至于订阅者,MQTT会控制显示消息,如果收到了PUBREL就认为消息已经收到,只需要在发送PUBCOMP,完成MQTT Qos 2 的交互流程。
总结:
在Qos 1 情况下,如果PUBACK超时或者发送失败,就会重传消息;
在Qos 2情况下,在没有收到PUBREC之前,也是有消息重传的可能,但是在接收到PUBREC报文之后,主题消息被删除,这样就不会对下面的交互过程产生干扰,消息在这一步之后,只能重传PUBREC、PUBREL报文。