本节摘要:链路稳定之后,报文说什么、怎么说,由应用层协议决定。MQTT 与 HTTP 是物联网世界里使用率最高的两套协议,但它们的心智模型截然不同:一个是常连的发布订阅,一个是一次性的请求应答。本节讲透两者的适配场景、MQTT 三档 QoS 的真实语义、主题与载荷的工程规范,以及一次"数据重复上报"引发的 QoS 选型复盘。
6.1 节管好了链路这条公路;这一节选运输工具。协议选型错误不会让系统跑不起来——这恰是它的危险之处:错误的协议能"正常工作",直到流量、设备数或可靠性要求上来才显形。所以在写第一行联网代码之前,先把两套模型的边界想清楚。
MQTT 是邮局模型:设备与服务器(代理)保持一条长连接,所有人都往"主题"里投递或订阅消息。发消息的人不知道谁会收到,收消息的人不需要轮询——代理负责按订阅关系分发。长连接意味着设备上线即可收命令(服务器主动推送),延迟低、开销小(最小报文两字节),这是它为物联网生的证据。
HTTP 是上门取件模型:客户端发起请求,服务器应答,交易完成即分手。无状态、直白、调试友好、穿透一切防火墙——代价是每次交易都要建连拆连,服务器无法主动找设备(设备必须不停轮询)。设备端还要为每个请求拼完整的报文头,几百字节起步。
选型判据因此清晰:服务器要不要主动找设备? 要(远程控制、配置下发、实时告警),MQTT 几乎是唯一解。不要(偶尔上传一条日志、低频拉取配置、对接第三方接口),HTTP 更简单直接。混合使用完全正当:遥测与命令走 MQTT,固件下载与文件传输走 HTTP——各取所长。
MQTT 的服务质量档位是最常被误读的部分。三档说的是代理与应用之间的确认链条,不是魔法:
| 档位 | 语义 | 代价 | 适配场景 |
|---|---|---|---|
| QoS0 | 最多一次,发完就忘 | 无确认 | 高频遥测,丢一帧无所谓 |
| QoS1 | 至少一次,确认后才算数 | 可能重复 | 告警、状态变更,宁重复不丢失 |
| QoS2 | 恰好一次,两阶段确认 | 往返最多、状态最重 | 计费类动作,重复即事故 |
误读的重灾区在"至少一次":它承诺不丢,不承诺不重——网络抖动时发送方没等到确认就重发,接收方可能收到两份。业务代码必须自带幂等:每条消息带唯一编号,接收方按编号去重。指望 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 节亲手设计协议帧的路,本书更推荐先评估协议本身的适配性。
⚠️ 常见坑:主题层级里用中文或空格,某些代理与客户端组合会静默失败。主题全用小写英文加数字,层级分隔只用斜杠——这套洁癖会在某次联调里救你一晚。
💡 关键直觉:选协议就是选"谁主动"。服务器要主动,长连接的发布订阅;客户端主动就好,无状态的请求应答。想通这一句,一半的协议争论可以散场。