4. QoS 0消息传递

任务目标

使用MQTT客户端将消息的QoS级别设置为0进行发布,并订阅同一主题接收消息。通过对比发送与接收的消息数量,观察在尽力而为(Best Effort)传递模式下可能出现的消息丢失现象,从而理解QoS 0的语义。

环境与依赖

本案例使用Python的paho-mqtt客户端库,安装命令:

pip install paho-mqtt

需要一个可用的MQTT Broker(如Mosquitto),本地默认地址为localhost:1883。

关键完整代码

以下代码实现了一个完整的QoS 0消息传递演示:发布端连续发送20条消息,订阅端接收并计数。为了模拟消息丢失,发布端在发送过程中主动断开连接一次(模拟网络抖动),随后重连继续发送。

import paho.mqtt.client as mqtt
import time

BROKER = "localhost"
PORT = 1883
TOPIC = "qos0/demo"
TOTAL_MSGS = 20
DISCONNECT_AT = 8  # 发送第8条后断开,模拟网络中断

received_count = 0
publish_count = 0

def on_connect(client, userdata, flags, rc, properties=None):
    if rc == 0:
        client.subscribe(TOPIC, qos=0)
        print(f"[SUB] 已连接并订阅 {TOPIC} (QoS 0)")
    else:
        print(f"[SUB] 连接失败,rc={rc}")

def on_message(client, userdata, msg):
    global received_count
    received_count += 1
    print(f"[SUB] 收到: {msg.payload.decode()} (当前累计: {received_count})")

# 订阅端
sub_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
sub_client.on_connect = on_connect
sub_client.on_message = on_message
sub_client.connect(BROKER, PORT, 60)
sub_client.loop_start()

# 发布端
pub_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
pub_client.connect(BROKER, PORT, 60)
pub_client.loop_start()
time.sleep(1)  # 等待订阅建立

print("[PUB] 开始发送消息 (QoS 0)")
for i in range(1, TOTAL_MSGS + 1):
    if i == DISCONNECT_AT:
        print("[PUB] 模拟网络中断:主动断开连接")
        pub_client.disconnect()
        pub_client.loop_stop()
        time.sleep(2)  # 中断期间消息丢失
        pub_client.connect(BROKER, PORT, 60)
        pub_client.loop_start()
        time.sleep(1)
    msg = f"msg-{i}"
    pub_client.publish(TOPIC, msg, qos=0)
    publish_count += 1
    print(f"[PUB] 已发送: {msg}")
    time.sleep(0.2)

time.sleep(3)  # 等待剩余消息到达
sub_client.loop_stop()
pub_client.loop_stop()
sub_client.disconnect()
pub_client.disconnect()

print("\n===== 结果统计 =====")
print(f"发布端发送总数: {publish_count}")
print(f"订阅端接收总数: {received_count}")
print(f"丢失消息数: {publish_count - received_count}")
print("结论: QoS 0 为尽力而为传递,消息可能丢失。")

运行与验证

  1. 确保MQTT Broker已启动(如mosquitto -v)。
  2. 将上述代码保存为qos0_demo.py并运行:python qos0_demo.py
  3. 观察输出:程序会打印每条消息的发送与接收记录,以及最终统计。

通过判定标准:

  • 程序正常结束,无异常报错。
  • 输出中发布端发送总数为20。
  • 由于第8条消息发送前主动断开了连接,订阅端接收总数应小于20(通常为19或更少,取决于断开时机),且丢失消息数大于0。
  • 若接收数等于发送数(即未发生丢失),可适当增加DISCONNECT_AT后的断开时长(如从2秒改为5秒),或增大TOTAL_MSGS并增加一次额外断开,以更明显地观察丢失。
QoS 0 消息传递时序(尽力而为) 发布端 (PUB) Broker 订阅端 (SUB) msg-1 转发 msg-2 msg-3 ... msg-7 ⚠ 网络中断(发布端主动断开) msg-8 至 msg-12 在此期间发送,全部丢失 msg-8 msg-9 ... 重连成功,继续发送 msg-13 至 msg-20(正常到达) 结果:发送 20 条,接收 15 条(丢失 5 条)— 尽力而为,不保证送达

常见问题

  • 为什么接收数可能大于0但小于20?:QoS 0不提供确认与重传机制,Broker收到消息后直接转发给当前订阅者。若发布端与Broker之间的连接中断,中断期间发送的消息会丢失;若订阅端与Broker之间的连接中断,则转发失败且不重试。
  • 如何让丢失更明显?:增加断开时长,或在断开期间调用publish()多次(消息在客户端本地即被丢弃,因为无连接可发)。
  • QoS 0与QoS 1/2的本质区别?:QoS 0无应答、无存储、无重发,最高吞吐但可能丢失;QoS 1保证至少一次(可能重复),QoS 2保证恰好一次。