1. MQTT协议基础回顾:版本对比、核心概念与报文结构
各位同学,咱们开始第一讲。MQTT这东西,说白了就是物联网世界的“普通话”。我最早接触它是在2015年做一个智能路灯项目,当时设备端资源紧张得要命,HTTP根本跑不动,这才发现MQTT的好。今天咱们先把地基打牢,后面讲桥接和集群才不慌。
1.1 MQTT协议版本对比:你该选哪个?
MQTT目前主流有三个版本:v3.1、v3.1.1(也叫MQTT 3.1.1)和v5.0。我个人习惯把v3.1.1称为“经典版”,v5.0叫“增强版”。
| 特性 | v3.1 | v3.1.1 | v5.0 |
|---|---|---|---|
| 协议标准 | IBM草案 | OASIS标准 | OASIS标准 |
| 会话过期 | 不支持 | 不支持 | 支持 |
| 用户属性 | 无 | 无 | 支持(自定义元数据) |
| 原因码 | 无 | 无 | 详细错误原因 |
| 订阅标识符 | 无 | 无 | 支持 |
| 流量控制 | 无 | 无 | 支持(Receive Maximum) |
我的建议:新项目直接上v5.0。v3.1.1虽然稳定,但v5.0的“用户属性”和“原因码”在调试时太香了。我曾经在一个车联网项目里,就因为v3.1.1没有原因码,排查断连问题花了整整两天——后来升级到v5.0,错误原因一目了然。
注意:如果对接的是老旧设备(比如2016年以前的模组),它们可能只支持v3.1。这时候别硬上v5.0,兼容性会出问题。我踩过这个坑。
1.2 核心概念:发布/订阅、主题、QoS
1.2.1 发布/订阅模型
MQTT用的是发布/订阅模式,不是传统的客户端/服务器请求-响应。你想想看,在物联网场景里,传感器可能成千上万,如果每个设备都直接跟服务器“点对点”通信,服务器早崩了。
发布/订阅的核心是“解耦”:
- 发布者只管发消息,不关心谁收
- 订阅者只管收消息,不关心谁发
- Broker(代理)负责转发,是中间人
我记得有一次做智慧农业项目,温度传感器每5秒发一次数据,同时有3个后台服务订阅这些数据做不同处理。如果用HTTP,传感器得同时给3个服务发请求,流量翻3倍。用MQTT,传感器只发一次,Broker负责复制分发——省带宽、省电量。
1.2.2 主题(Topic)
主题是MQTT消息的路由标签,结构类似文件路径:
sensor/temperature/room1
sensor/humidity/room1
actuator/light/room1/switch
主题支持通配符,这是MQTT的精髓之一:
- +:单级通配符,匹配一层。比如订阅
sensor/+/room1能收到sensor/temperature/room1和sensor/humidity/room1 - #:多级通配符,匹配剩余所有层级。比如订阅
sensor/#能收到所有 sensor 开头的消息
实战技巧:主题设计要“从宽到窄”。我习惯用 项目/设备类型/设备ID/数据类别 这种结构。比如 factory/pump/001/temperature。千万别用 data/1/2/3 这种无意义层级,后期维护会疯掉。
1.2.3 服务质量(QoS)
QoS是MQTT保证消息可靠性的机制,分三个等级:
| 等级 | 含义 | 发送次数 | 适用场景 |
|---|---|---|---|
| QoS 0 | 最多发一次(可能丢) | 1次 | 传感器高频数据,丢几条无所谓 |
| QoS 1 | 至少发一次(可能重复) | 至少1次,有ACK | 控制指令,但能接受重复 |
| QoS 2 | 恰好发一次(最可靠) | 4次握手 | 计费、交易等关键数据 |
为什么会这样?QoS 0就是“发完不管”,网络不好就丢了。QoS 1会等Broker确认,没收到确认就重发,所以可能重复。QoS 2用了两轮确认(PUBLISH→PUBREC→PUBREL→PUBCOMP),保证不丢不重。
避坑指南:我曾经在一个项目里对所有消息都用QoS 2,结果Broker CPU飙到90%。QoS 2开销很大,非必要别用。一般建议:传感器数据用QoS 0,控制指令用QoS 1,只有资金类数据才用QoS 2。
1.3 报文结构解析:MQTT包长什么样?
MQTT报文结构很紧凑,这是它适合物联网的原因。每个报文由三部分组成:
- 固定头(所有报文都有)
- 可变头(部分报文有)
- 有效载荷(部分报文有)
1.3.1 固定头
固定头至少2个字节:
字节1: 报文类型 + 标志位
- 高4位: 报文类型(CONNECT=1, PUBLISH=3, SUBSCRIBE=8 等)
- 低4位: 标志位(如DUP、QoS、RETAIN)
字节2+: 剩余长度(可变长编码)
- 每个字节最高位表示是否还有后续字节
- 低7位存储数据,最多4个字节
剩余长度的编码很有意思,它用了一种“连续字节”的方式:
例如:长度 128(0x80)
二进制: 00000001 00000000
编码后: 10000000 00000001
(第一个字节最高位=1,表示还有后续;第二个字节最高位=0,表示结束)
嗯,这里要注意:剩余长度最大能表示268435455字节(约256MB),但实际MQTT报文很少超过几MB。
1.3.2 可变头
可变头的内容取决于报文类型。以最常用的PUBLISH报文为例:
可变头包含:
- 主题名(UTF-8编码,前2字节是长度)
- 报文标识符(仅QoS 1和QoS 2需要,2字节)
CONNECT报文的可变头则包含协议名、协议级别、连接标志、保活时间等。我记得刚开始学的时候,老搞不清可变头和有效载荷的区别——简单说,可变头是“元数据”,有效载荷是“数据本身”。
1.3.3 有效载荷
有效载荷就是消息内容。对于PUBLISH报文,它就是应用数据,可以是任意二进制。对于SUBSCRIBE报文,它包含订阅的主题过滤器列表。
举个例子,一个完整的PUBLISH报文(QoS 0,主题"test",内容"hello")的十六进制:
30 0D 00 04 74 65 73 74 68 65 6C 6C 6F
| | | | | | | | | | | | |
| | | | t e s t h e l l o
| | | └─ 主题长度=4
| | └─ 主题长度高字节=0
| └─ 剩余长度=13
└─ 固定头: 0x30 (PUBLISH, QoS=0)
核心要点:MQTT报文设计追求“最小开销”。一个QoS 0的PUBLISH报文,固定头+可变头最少只需4字节开销(2字节固定头+2字节主题长度),剩下的全是有效载荷。这在窄带宽场景下优势巨大。
好了,第一讲就到这里。MQTT的基础概念虽然简单,但每个细节都值得深挖。下一讲咱们开始聊桥接——为什么需要桥接?桥接和集群有什么区别?到时候见。
公众号:蓝海资料掘金营,微信deep3321