第3章 MQTT核心概念详解
3.1 发布/订阅模式
模式对比
MQTT发布-订阅模式
消息
消息
转发
转发
转发
发布者
Broker
发布者
订阅者1
订阅者2
订阅者3
传统客户端-服务器模式
请求
请求
请求
响应
响应
响应
客户端1
服务器
客户端2
客户端3
发布/订阅工作流程
订阅者B 订阅者A Mosquitto Broker 发布者 订阅者B 订阅者A Mosquitto Broker 发布者 1. 订阅主题 2. 发布消息 3. 发布者与订阅者 完全解耦 SUBSCRIBE home/temperature QoS: 1 SUBACK QoS授予: 1 SUBSCRIBE home/ SUBACK QoS授予: 2 PUBLISH home/temperature QoS: 1 Payload: "25°C" PUBLISH home/temperature QoS: 1 Payload: "25°C" PUBLISH home/temperature QoS: 2 Payload: "25°C"
发布/订阅的优势
| 特性 | 传统模式 | 发布/订阅模式 |
|---|---|---|
| 耦合性 | 紧耦合(客户端需知道服务器地址) | 松耦合(发布者无需知道订阅者) |
| 扩展性 | 受限于服务器处理能力 | 通过Broker轻松扩展 |
| 灵活性 | 需要修改代码添加新客户端 | 动态订阅即可 |
| 网络拓扑 | 点对点 | 多对多 |
| 时间耦合 | 同步通信 | 异步通信 |
消息流转过程
是
否
QoS 0
QoS 1
QoS 2
发布者发送消息
建立TCP连接
PUBLISH报文
Mosquitto Broker
主题匹配
有匹配订阅?
消息入队
丢弃消息
QoS等级?
发送一次
不确认
发送后等待
PUBACK
完整握手
PUBREC/PUBREL/PUBCOMP
完成
3.2 主题(Topic)详解
主题命名规范
主题命名规范
基本规则
大小写敏感
推荐
不能包含空格
UTF-8编码
层级设计
从大到小
语义清晰
层级适中
避免过深
命名建议
使用小写
连词符-分隔
避免特殊字符
保留前缀$
主题层级示例
智能家居主题树:
home/ # 根主题
├── livingroom/ # 客厅
│ ├── temperature # 温度
│ ├── humidity # 湿度
│ ├── light # 灯光
│ └── aircon # 空调
├── bedroom/ # 卧室
│ ├── temperature
│ ├── light
│ └── curtain # 窗帘
├── kitchen/ # 厨房
│ ├── temperature
│ ├── smoke # 烟雾
│ └── door # 门磁
└── gateway/ # 网关
├── status
└── connectivity
通配符使用
通配符类型
- 单级通配符
多级通配符
home/+/temperature
sensor/+/data
device/+/status
home/#
sensor/floor1/#
匹配:
home/livingroom/temperature
home/bedroom/temperature
不匹配:
home/livingroom/temperature/detail
匹配:
home/
home/livingroom
home/livingroom/temperature
通配符使用规则
| 通配符 | 位置 | 匹配规则 | 示例 | 匹配 | 不匹配 |
|---|---|---|---|---|---|
| + | 任意 | 单个层级 | home/+/temp |
home/livingroom/temp |
home/livingroom/temp/detail |
| # | 末尾 | 多个层级 | home/# |
home, home/a, home/a/b |
hometest |
使用示例:
bash
# 订阅所有温度传感器
mosquitto_sub -v -t "home/+/temperature"
# 订阅客厅所有设备
mosquitto_sub -v -t "home/livingroom/#"
# 订阅所有楼层状态
mosquitto_sub -v -t "building/+/status"
# 订阅所有消息(谨慎使用)
mosquitto_sub -v -t "#"
系统保留主题
保留主题
SYS - 系统信息
share - 共享订阅
SYS/broker/version
SYS/broker/clients/connected
SYS/broker/load/messages/...
$share/group1/topic
$SYS 主题示例:
bash
# 查看所有系统主题
mosquitto_sub -v -t "$SYS/#"
# 常用系统主题
$SYS/broker/version # Broker版本
$SYS/broker/uptime # 运行时间
$SYS/broker/timestamp # 时间戳
$SYS/broker/clients/connected # 连接的客户端数
$SYS/broker/clients/disconnected # 断开的客户端数
$SYS/broker/messages/received # 接收的消息数
$SYS/broker/messages/sent # 发送的消息数
$SYS/broker/bytes/received # 接收的字节数
$SYS/broker/bytes/sent # 发送的字节数
3.3 QoS质量等级
QoS等级概览
QoS等级
QoS 0 - 最多一次
QoS 1 - 至少一次
QoS 2 - 恰好一次
🚀 性能最优
⚠️ 可能丢失
📡 适合不重要数据
✅ 保证送达
⚠️ 可能重复
📡 平衡性能
✅✅ 保证送达
✅✅ 不重复
🐌 性能开销大
QoS工作流程对比
订阅者 Broker 发布者 订阅者 Broker 发布者 QoS 0 - 最多一次 不需要确认,可能丢失 QoS 1 - 至少一次 需要确认,可能重复 QoS 2 - 恰好一次 四次握手,保证不重复 PUBLISH (QoS 0) PUBLISH (QoS 0) PUBLISH (QoS 1, PacketId=1) PUBACK (PacketId=1) PUBLISH (QoS 1, PacketId=2) PUBACK (PacketId=2) PUBLISH (QoS 2, PacketId=3) PUBREC (PacketId=3) PUBREL (PacketId=3) PUBCOMP (PacketId=3) PUBLISH (QoS 2, PacketId=4) PUBREC (PacketId=4) PUBREL (PacketId=4) PUBCOMP (PacketId=4)
QoS决策流程
不重要
重要
是
否
需要发送消息
数据重要性?
QoS 0
最多一次
需要严格
不重复?
QoS 2
恰好一次
QoS 1
至少一次
示例:
温度传感器
心跳检测
实时位置
示例:
告警消息
控制指令
配置更新
示例:
计费数据
金融交易
关键日志
选择完成
QoS使用示例
bash
# QoS 0 - 最多一次(性能最高)
mosquitto_pub -t "sensor/temperature" -m "25.5" -q 0
# QoS 1 - 至少一次(推荐)
mosquitto_pub -t "alert/fire" -m "Fire detected!" -q 1
# QoS 2 - 恰好一次(严格保证)
mosquitto_pub -t "billing/payment" -m "Payment: $100" -q 2
# 订阅时指定最大QoS
mosquitto_sub -t "sensor/#" -q 1
QoS协议交互流程
QoS 0 流程
发布者发送
转发
投递
完成
PUBLISH
Broker
Subscriber
火力全发,不管结果
可能丢失消息
QoS 1 流程
发布者发送
Broker确认
完成
超时重传
重试成功
PUBLISH
PUBACK
需要确认
可能重复送达
QoS 2 流程
发布者发送
Broker收到
发布者释放
Broker完成
成功
超时重传
超时重传
PUBLISH
PUBREC
PUBREL
PUBCOMP
四次握手
保证恰好一次
开销最大
QoS选择建议表
| 应用场景 | 推荐QoS | 理由 |
|---|---|---|
| 温度/湿度传感器 | QoS 0 | 数据更新快,丢失一两次影响不大 |
| 烟雾/火灾告警 | QoS 1 | 必须送达,可接受重复 |
| 远程控制指令 | QoS 1 | 确保执行,重复执行通常安全 |
| 计费/交易数据 | QoS 2 | 不能遗漏也不能重复 |
| 心跳/在线状态 | QoS 0 | 频繁发送,性能优先 |
| 固件更新通知 | QoS 1 | 需要送达,可重试 |
| GPS位置追踪 | QoS 0 | 实时性优先,数据量大 |
3.4 保留消息(Retained Messages)
保留消息机制
订阅者2(后订阅) 订阅者1(先订阅) Broker 发布者 订阅者2(后订阅) 订阅者1(先订阅) Broker 发布者 发布保留消息 保存消息 先订阅的客户端 后订阅的客户端 每个新订阅者 都会收到保留消息 PUBLISH topic Retained=TRUE Payload="状态:在线" SUBSCRIBE topic PUBLISH topic Payload="状态:在线" SUBSCRIBE topic PUBLISH topic Payload="状态:在线"
保留消息应用场景
保留消息用途
设备状态
最后已知位置
在线/离线状态
配置版本
传感器数据
最新读数
当前温度
当前湿度
配置信息
默认设置
系统参数
功能开关
通知公告
系统公告
维护通知
更新提示
保留消息使用示例
bash
# 发布保留消息 - 设备状态
mosquitto_pub -t "device/gateway001/status" -m "online" -r
# 发布保留消息 - 最新温度
mosquitto_pub -t "sensor/livingroom/temperature" -m "25.5" -r
# 新订阅者会立即收到保留消息
mosquitto_sub -t "sensor/livingroom/temperature" -C 1
# 清除保留消息(发送空payload)
mosquitto_pub -t "device/gateway001/status" -r -n
# 查看$SYS中的保留消息
mosquitto_sub -v -t "$SYS/broker/retained messages/count"
保留消息注意事项
保留消息特性
✅ 每个主题只保留最后一条
✅ 新订阅者立即收到
⚠️ 会占用Broker内存
⚠️ 清除需发送空消息
应用: 设备最新状态
应用: 快速获取配置
注意: 避免滥用
方法: 使用-r -n清除
3.5 遗嘱消息(Last Will)
遗嘱消息工作原理
客户端连接
设置遗嘱
正常断开
DISCONNECT
异常断开
超时/网络故障
遗嘱不发送
Broker发送遗嘱消息
Connected
Disconnected
WillTriggered
PublishWill
异常断开时
自动发送预设消息
遗嘱消息流程
订阅者 Broker 客户端 订阅者 Broker 客户端 连接时设置遗嘱 正常运行 异常断开! 检测到客户端断开 收到离线通知 CONNECT Will Topic: device/status Will Message: "offline" Will QoS: 1 PUBLISH sensor/data "25°C" 转发数据 PUBLISH device/status "offline" (遗嘱消息)
遗嘱消息使用示例
bash
# 连接时设置遗嘱消息
mosquitto_sub \
-t "sensor/data" \
-id "sensor001" \
-will-topic "device/sensor001/status" \
-will-payload "offline" \
-will-qos 1 \
-will-retain
# 在另一个终端监控设备状态
mosquitto_sub -v -t "device/#"
# 当客户端异常断开时
# 自动收到: device/sensor001/status offline
遗嘱消息应用场景
遗嘱消息场景
设备离线通知
网关掉线
传感器故障
移动设备离线
状态变更
在线→离线
活跃→休眠
正常→异常
告警触发
看门狗超时
心跳丢失
连接中断
清理状态
清除临时数据
释放资源
更新UI状态
遗嘱消息配置参数
| 参数 | 说明 | 示例 |
|---|---|---|
| Will Topic | 遗嘱消息主题 | device/status |
| Will Message | 遗嘱消息内容 | "offline" |
| Will QoS | 遗嘱QoS等级 | 1 |
| Will Retain | 是否保留遗嘱 | true |
3.6 Clean Session与持久会话
会话状态对比
Clean Session = false (持久会话)
客户端连接
创建持久会话
客户端断开
保留会话状态
缓存消息
重连时恢复
Clean Session = true (临时会话)
客户端连接
创建临时会话
客户端断开
立即清理会话
丢弃未发送消息
会话状态内容
会话状态
客户端信息
ClientID
用户名
订阅列表
消息队列
QoS 1未确认
QoS 2已收到
待发送消息
会话参数
Clean Session标志
Will遗嘱配置
保活时间
Clean Session使用场景
频繁在线
间歇在线
移动设备
选择会话类型
Clean Session=true
Clean Session=false
视情况而定
应用:
实时传感器
手机APP
临时监控
应用:
远程设备
水电表
农业传感器
考虑:
网络稳定性
消息重要性
电池寿命
✅ 减少服务器负担
✅ 离线消息不丢失
⚖️ 权衡性能与可靠性
会话恢复流程
Broker 客户端 Broker 客户端 首次连接 订阅主题 离线... 有QoS 1/2消息到达 缓存到会话队列 重连 发送缓存的离线消息 CONNECT CleanSession=false ClientID=device001 CONNACK 会话创建 SUBSCRIBE sensor/ SUBACK CONNECT CleanSession=false ClientID=device001 CONNACK 恢复会话 PUBLISH sensor/data (QoS 1) PUBACK
会话配置示例
bash
# Clean Session = true (默认)
mosquitto_sub -t "sensor/#" -c
# Clean Session = false (持久会话)
mosquitto_sub -t "sensor/#" -id "persistent_client" -q 1
# 重连恢复会话
mosquitto_sub -t "sensor/#" -id "persistent_client"
3.7 Keep Alive(心跳机制)
心跳机制原理
Subscribers Broker 客户端 Subscribers Broker 客户端 建立连接 Keep Alive = 60秒 loop [每60秒] 网络故障... 1.5 x 60 = 90秒 未收到PINGREQ PINGREQ PINGRESP 判断客户端离线 发送遗嘱消息
Keep Alive时间设置
稳定有线网络
移动网络
不稳定网络
特别稳定
Keep Alive设置
网络环境?
60秒
30秒
15-20秒
120秒
✅ 减少流量
⚖️ 平衡流量与响应
📡 快速检测断线
🚀 最小开销
建议: 根据实际
环境测试调整
Keep Alive计算
bash
# Keep Alive设置原则
# 最小值: 5秒(协议限制)
# 最大值: 约18小时(协议限制)
# 推荐值: 15-120秒
# 示例
mosquitto_sub -t "test/#" -k 30 # 30秒心跳
mosquitto_pub -t "test" -m "hello" -k 60 # 60秒心跳
3.8 Client ID规范
Client ID作用
Client ID
唯一标识客户端
关联持久会话
断线重连识别
ACL权限控制
必需: 1-23字符
持久会话必须固定
重连使用相同ID
权限配置依据
Client ID命名规范
Client ID命名
唯一性
全局唯一
不能重复
避免冲突
可读性
语义清晰
包含设备信息
便于调试
格式建议
前缀-设备ID-序号
小写字母+数字
连字符分隔
示例格式
dev-sensor001
app-user123-phone
gw-floor1-room2
Client ID生成策略
固定设备
移动应用
临时客户端
生成Client ID
设备类型?
设备唯一标识
MAC/序列号
用户ID+设备ID
随机生成
示例:
sensor-aabbccdd
gw-001-001
示例:
app-user123-ios
mobile-user456
示例:
temp-uuid123
client-random
✅ 稳定可追溯
✅ 用户关联
⚠️ 仅临时会话
持久会话推荐
临时会话可用
3.9 本章小结
核心概念总结
MQTT核心概念
发布订阅
主题设计
QoS等级
保留消息
遗嘱消息
会话管理
心跳机制
Client ID
解耦通信
层级清晰
3种QoS
最新状态
异常通知
离线消息
保持连接
唯一标识
最佳实践清单
| 概念 | 最佳实践 | 注意事项 |
|---|---|---|
| 主题设计 | 层级化、语义化 | 避免过深、使用通配符 |
| QoS选择 | 根据数据重要性 | 平衡性能与可靠性 |
| 保留消息 | 设备状态、配置 | 及时清理、避免滥用 |
| 遗嘱消息 | 离线通知、状态变更 | 谨慎设置retain标志 |
| 会话类型 | 在线设备用clean | 离线设备用持久 |
| 心跳时间 | 根据网络调整 | 太频繁浪费资源 |
| Client ID | 全局唯一、有意义 | 持久会话必须固定 |
🎯 思考与练习
理论题
- QoS 1 和 QoS 2 的主要区别是什么?什么场景应该使用 QoS 2?
- 保留消息和遗嘱消息有什么区别?各自适合什么场景?
- Clean Session 设置为 true 和 false 对消息接收有什么影响?
实践题
- 设计一个智能家居的主题结构,包含温度、湿度、灯光等设备
- 测试不同 QoS 等级在网络中断时的表现
- 配置遗嘱消息,观察客户端异常断开时的行为
进阶题
- 实现一个设备状态监控系统,使用保留消息和遗嘱消息
- 编写脚本测试持久会话的离线消息恢复
- 分析共享订阅在负载均衡中的应用