3. MQTT协议精要

在CoAP与MQTT协议转换工程中,MQTT一侧的行为几乎全部由六个机制决定:发布/订阅模型决定消息如何路由,主题与通配符决定路由的匹配规则,QoS等级决定投递保证强度,会话保持决定断线后状态是否延续,遗嘱消息决定异常断线时如何通知他人,保留消息决定新订阅者能否立即拿到最新状态。理解这六者,才能判断“一条CoAP请求转成MQTT发布后,究竟会被谁收到、收到几次、断线后还在不在”。

发布/订阅模型:解耦的三角关系

MQTT不使用客户端直连客户端的通信方式,而是引入Broker(代理服务器)作为中心。发布者(Publisher)只负责把消息发到某个主题,订阅者(Subscriber)只负责声明自己关心哪些主题,双方互不知道对方的存在。这种解耦带来三个直接后果:一是空间解耦,发布者无需知道订阅者的地址;二是时间解耦,双方不必同时在线;三是数量解耦,一个发布可对应零个、一个或多个订阅者。

在转换工程中,这一点非常关键。CoAP通常是请求/响应式的点对点交互,而MQTT是单向发布。当CoAP客户端向某个资源发起GET时,转换网关往往需要把它映射为一次MQTT发布,再由订阅了对应主题的MQTT客户端接收。若没有订阅者,消息在Broker处可能被直接丢弃(QoS 0)或短暂保留(QoS 1/2重传),这与CoAP“请求必然有响应”的直觉不同,必须在网关设计中显式处理。

主题与通配符:层级化的路由地址

MQTT主题是UTF-8字符串,用斜杠“/”分隔层级,例如 factory/line1/temp。主题区分大小写,且不以“/”开头也合法,但建议统一规范。订阅时可以使用两种通配符:

  • “+”单层通配符:匹配一个层级。订阅 factory/+/temp 可收到 factory/line1/temp 和 factory/line2/temp,但收不到 factory/line1/room/temp。
  • “#”多层通配符:匹配零个或多个层级,只能出现在主题末尾。订阅 factory/# 可收到 factory/line1/temp、factory/line1/room/temp,也能收到 factory 本身。

主题设计应遵循“从粗到细、从稳定到易变”的原则。例如把设备类型、区域、设备ID、测点依次排列:plant/A/area3/sensor07/temperature。这样上层可用 plant/A/# 做区域聚合,下层可用 plant/A/area3/+/temperature 做同类测点批量订阅。转换网关在把CoAP资源路径映射为MQTT主题时,应保持这种层级一致性,否则通配符订阅会失效或误匹配。

QoS等级:投递保证的强度选择

MQTT定义了三个服务质量等级,它们描述的是“客户端与Broker之间”的投递保证,而不是端到端保证:

QoS语义交互过程典型适用
0最多一次PUBLISH,无确认高频传感器数据,允许丢失
1至少一次PUBLISH → PUBACK,可能重复一般控制指令,需到达但可容忍重复
2恰好一次PUBLISH → PUBREC → PUBREL → PUBCOMP计费、关键状态变更

需要特别注意:QoS 1可能造成重复投递,订阅端必须做幂等处理;QoS 2握手开销大,在高频数据场景下会显著增加延迟和Broker负载。在CoAP/MQTT转换中,CoAP的CON(可确认)消息通常映射为QoS 1,NON(非确认)映射为QoS 0;若CoAP侧使用块传输或观察模式,则需评估是否升级到QoS 2,但应避免无差别使用QoS 2。

会话保持:断线后的状态延续

MQTT客户端连接时可设置 Clean Session(MQTT 3.1.1)或 Clean Start 加会话过期时间(MQTT 5.0)。当Clean Session为false时,Broker会为客户端保存订阅关系和未确认的QoS 1/2消息;客户端重新连接后,可以继续接收离线期间积压的消息。若为true,则每次连接都是全新会话,之前的订阅和积压消息全部丢弃。

在转换网关中,如果网关需要保证“CoAP设备离线期间产生的MQTT指令不丢失”,就应使用持久会话,并配合QoS 1。但持久会话会占用Broker存储,且积压消息过多可能导致重连后瞬间洪泛,因此需要设置合理的会话过期时间和队列上限。

遗嘱消息与保留消息:异常通知与状态快照

遗嘱消息(Will Message)是客户端在连接时预先注册的一条消息,当Broker检测到该客户端异常断开(非正常DISCONNECT)时,会代为发布。它常用于设备离线告警:例如设备连接时注册遗嘱主题 plant/A/area3/sensor07/status,载荷为 offline,QoS 1,保留标志为true。一旦设备掉线,订阅者立即收到离线通知。

保留消息(Retained Message)则是Broker为某个主题保存的最后一条消息。新订阅者一旦订阅该主题,会立即收到这条保留消息,而无需等待下一次发布。它适合表达“当前状态”,例如设备在线状态、最新温度值。保留消息与遗嘱消息经常配合使用:遗嘱消息设置retain=true,这样设备离线后,新订阅者也能立刻看到“offline”状态。

注意:保留消息不会因为被消费而删除,只有发布一条空载荷的保留消息才能清除该主题的保留消息。在转换网关中,若CoAP资源被删除,应同步发布空载荷保留消息,避免MQTT侧残留过期状态。
MQTT 发布/订阅核心机制与转换映射 CoAP 客户端 Publisher 角色 MQTT Broker 主题路由 / QoS / 会话 / 保留 plant/A/area3/sensor07/temperature MQTT 订阅者 A plant/A/# MQTT 订阅者 B plant/+/area3/+/temperature PUBLISH 匹配 匹配 QoS 等级与投递保证 QoS 0:最多一次,无确认 QoS 1:至少一次,PUBACK QoS 2:恰好一次,四次握手 会话与特殊消息 Clean Session = false 遗嘱消息:异常断线通知 保留消息:最新状态快照 空载荷可清除保留消息 CoAP → MQTT 映射要点 • CON → QoS 1 • NON → QoS 0 • 资源路径 → 主题层级 • 观察模式 → 保留消息 • 设备离线 → 遗嘱消息 • 资源删除 → 空保留消息 • 幂等处理防QoS 1重复

六者协同:一个完整的转换判断链

把上述机制串起来,可以得到一条清晰的判断链。假设CoAP设备上报温度,转换网关执行以下决策:

  1. 主题构造:根据设备ID和测点生成 plant/A/area3/sensor07/temperature,保持层级稳定。
  2. QoS选择:若CoAP消息为CON,映射为QoS 1;若为NON,映射为QoS 0。
  3. 保留标志:温度属于“最新状态”,设置retain=true,使新订阅者立即获得当前值。
  4. 会话策略:网关自身以持久会话连接Broker,确保离线期间QoS 1消息不丢。
  5. 遗嘱注册:网关连接时注册遗嘱主题 plant/A/gateway/status,载荷 offline,retain=true,QoS 1。
  6. 订阅端幂等:订阅者收到QoS 1消息后,按消息ID或时间戳去重,避免重复执行控制指令。

这六步并非固定流程,而是六个可独立配置的维度。实际工程中,最常见的错误是:主题层级设计随意导致通配符订阅失效;QoS 1重复消息未做幂等导致指令重复执行;保留消息未清理导致新订阅者收到过期状态;遗嘱消息未设置retain导致离线通知只被当时在线的订阅者看到。理解每个机制的边界,才能在CoAP与MQTT之间做出正确的转换决策。

在调试阶段,可以用 mosquitto_sub -t 'plant/#' -v 观察所有主题的实时消息,再用 mosquitto_sub -t 'plant/A/area3/sensor07/temperature' -v -C 1 验证保留消息是否立即返回。若订阅后立刻收到一条消息,说明该主题存在保留消息;若等待后才收到,说明是实时发布。