6.2 应用层协议:MQTT 与 HTTP


6.2 应用层协议:MQTT 与 HTTP

本节摘要:链路稳定之后,报文说什么、怎么说,由应用层协议决定。MQTT 与 HTTP 是物联网世界里使用率最高的两套协议,但它们的心智模型截然不同:一个是常连的发布订阅,一个是一次性的请求应答。本节讲透两者的适配场景、MQTT 三档 QoS 的真实语义、主题与载荷的工程规范,以及一次"数据重复上报"引发的 QoS 选型复盘。

6.1 节管好了链路这条公路;这一节选运输工具。协议选型错误不会让系统跑不起来——这恰是它的危险之处:错误的协议能"正常工作",直到流量、设备数或可靠性要求上来才显形。所以在写第一行联网代码之前,先把两套模型的边界想清楚。

两套心智模型:邮局与上门取件

MQTT 是邮局模型:设备与服务器(代理)保持一条长连接,所有人都往"主题"里投递或订阅消息。发消息的人不知道谁会收到,收消息的人不需要轮询——代理负责按订阅关系分发。长连接意味着设备上线即可收命令(服务器主动推送),延迟低、开销小(最小报文两字节),这是它为物联网生的证据。

HTTP 是上门取件模型:客户端发起请求,服务器应答,交易完成即分手。无状态、直白、调试友好、穿透一切防火墙——代价是每次交易都要建连拆连,服务器无法主动找设备(设备必须不停轮询)。设备端还要为每个请求拼完整的报文头,几百字节起步。

选型判据因此清晰:服务器要不要主动找设备? 要(远程控制、配置下发、实时告警),MQTT 几乎是唯一解。不要(偶尔上传一条日志、低频拉取配置、对接第三方接口),HTTP 更简单直接。混合使用完全正当:遥测与命令走 MQTT,固件下载与文件传输走 HTTP——各取所长。

QoS 三档的真实含义

MQTT 的服务质量档位是最常被误读的部分。三档说的是代理与应用之间的确认链条,不是魔法:

档位 语义 代价 适配场景
QoS0 最多一次,发完就忘 无确认 高频遥测,丢一帧无所谓
QoS1 至少一次,确认后才算数 可能重复 告警、状态变更,宁重复不丢失
QoS2 恰好一次,两阶段确认 往返最多、状态最重 计费类动作,重复即事故

误读的重灾区在"至少一次":它承诺不丢,不承诺不重——网络抖动时发送方没等到确认就重发,接收方可能收到两份。业务代码必须自带幂等:每条消息带唯一编号,接收方按编号去重。指望 QoS1 替你挡住重复,是初学者的集体学费。

图:QoS1 的确认链与重复报文的产生

图:QoS1 的确认链与重复报文的产生

主题与载荷:别把邮编写在信纸上

MQTT 主题是分层的地址,斜杠分层是其全部精髓。工程规范三条:层级从粗到细(建筑到楼层到房间到设备);不要把设备的唯一编号编进主题层级(改一次命名全网遭殃,编号放载荷或用户名里);通配符只给订阅端用(发布端写通配符是协议错误)。

载荷的选型呼应 3.1 节的老话题:对人调试频繁的用 JSON,带宽敏感的高频遥测用二进制紧凑格式。一条工程红线:遥测载荷只放数据与时间戳,不放解释性文本——"温度有点高哦"这类人话进载荷,等于让每条消息替你写散文,流量与解析成本一起买单。

// MQTT 遥测发布:QoS1 加幂等编号的最小正确写法 #include <PubSubClient.h> extern PubSubClient mqtt; uint16_t seq = 0; bool publishTelemetry(float temp, float humi) { char payload[96]; snprintf(payload, sizeof(payload), "{\"id\":%u,\"ts\":%lu,\"t\":%.1f,\"h\":%.1f}", seq, millis() / 1000, temp, humi); // 序号与时间戳随行 bool ok = mqtt.publish("plant/floor3/room12/telemetry", payload, true); // retained 让新订阅者立刻拿到最新值 if (ok) seq++; return ok; }

输出是一条带自增编号的 JSON 遥测。retain 标志值得多说一句:它让代理保存这条消息、新订阅者一上线立即收到——设备状态类消息用它,云端"刚上线就看到当前状态"的体验全靠它;但遥测类高频消息慎用,代理只存最后一条,可这正是你要的。

案例:数据重复上报的选型复盘

背景:能耗监测项目把 QoS0 换成 QoS1 求稳,结果云端电表读数偶发"倒退"——重复报文里的旧值覆盖了新值。

操作:先定位重复来源:云端日志确认同一报文编号出现两次,正是 QoS1 的确认丢失重发所致。三个改进按代价递增落地:云端按编号去重(治标);发送侧把"读数型遥测"降回 QoS0 加 retain——计量数据的正确性由内容(时间戳与读数)保证,不靠链路确认;把"告警类消息"保留 QoS1 并在云端幂等。计费动作这种真需要恰好一次的,改由云端聚合账本按日结算,设备侧不做 QoS2。

结果:读数倒退归零,整体流量反而下降——QoS1 的确认开销从高频遥测上省了下来。

解读:这起复盘的教训浓缩成一句:QoS 档位应该按消息的业务性质分类设定,而不是全链路一刀切。读数是"新的覆盖旧的"(幂等天然成立),告警是"一次都不能少",计费是"一次就是一次"——三种性质对应三档 QoS,业务代码再补幂等兜底。协议的可靠性语义与业务的正确性要求对齐了,系统才真正可靠。

变式:受限网络(NB-IoT、LoRaWAN)上 MQTT 的长连接与确认开销太奢侈,MQTT-SN 或直接裸 UDP 加自定义确认帧更合适——但那等于回到 3.1 节亲手设计协议帧的路,本书更推荐先评估协议本身的适配性。

⚠️ 常见坑:主题层级里用中文或空格,某些代理与客户端组合会静默失败。主题全用小写英文加数字,层级分隔只用斜杠——这套洁癖会在某次联调里救你一晚。

💡 关键直觉:选协议就是选"谁主动"。服务器要主动,长连接的发布订阅;客户端主动就好,无状态的请求应答。想通这一句,一半的协议争论可以散场。

本节要点回顾

  • MQTT 是邮局(长连接、订阅推送),HTTP 是上门取件(无状态、一次性),按"服务器要不要主动"选型;
  • QoS1 不承诺不重:确认丢失重发必然产生重复,业务幂等是标配而非选项;
  • QoS 按消息性质分类设定:遥测 QoS0 加 retain、告警 QoS1、计费上云端聚合;
  • 主题三层规范:从粗到细、编号不进层级、通配符只给订阅端;
  • 载荷只放数据与时间戳,retain 用于状态类消息。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U