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设备上报温度,转换网关执行以下决策:
- 主题构造:根据设备ID和测点生成
plant/A/area3/sensor07/temperature,保持层级稳定。 - QoS选择:若CoAP消息为CON,映射为QoS 1;若为NON,映射为QoS 0。
- 保留标志:温度属于“最新状态”,设置retain=true,使新订阅者立即获得当前值。
- 会话策略:网关自身以持久会话连接Broker,确保离线期间QoS 1消息不丢。
- 遗嘱注册:网关连接时注册遗嘱主题
plant/A/gateway/status,载荷offline,retain=true,QoS 1。 - 订阅端幂等:订阅者收到QoS 1消息后,按消息ID或时间戳去重,避免重复执行控制指令。
这六步并非固定流程,而是六个可独立配置的维度。实际工程中,最常见的错误是:主题层级设计随意导致通配符订阅失效;QoS 1重复消息未做幂等导致指令重复执行;保留消息未清理导致新订阅者收到过期状态;遗嘱消息未设置retain导致离线通知只被当时在线的订阅者看到。理解每个机制的边界,才能在CoAP与MQTT之间做出正确的转换决策。
mosquitto_sub -t 'plant/#' -v 观察所有主题的实时消息,再用 mosquitto_sub -t 'plant/A/area3/sensor07/temperature' -v -C 1 验证保留消息是否立即返回。若订阅后立刻收到一条消息,说明该主题存在保留消息;若等待后才收到,说明是实时发布。