MQTT协议使用手册

适用对象 :初中级物联网开发工程师、嵌入式开发者、后端开发人员、IoT平台接入工程师

协议重点 :MQTT 3.1.1,兼顾 MQTT 5.0

实践重点:Broker 部署、设备接入、Topic 设计、QoS、心跳、LWT、Retain、会话、安全与故障排查


1. 前言与概述

1.1 MQTT 是什么?

MQTT(Message Queuing Telemetry Transport) 是一种轻量级的、基于发布/订阅模型的消息传输协议。

它最初由 IBM 的 Andy Stanford-Clark 和 Arlen Nipper 等人推动,用于资源受限、网络质量不稳定的设备之间进行消息通信。

MQTT 的核心思想可以概括成一句话:

设备不直接找设备通信,而是把消息交给 Broker,由 Broker 按 Topic 将消息转发给订阅者。

典型结构:

复制代码
                    MQTT Broker
                 ┌───────────────┐
                 │               │
      发布        │               │       订阅
设备 A ──────────►│    Broker     │──────────► 后端服务器
                 │               │
      发布        │               │       订阅
设备 B ──────────►│               │──────────► 手机APP
                 │               │
                 └───────────────┘
                       ▲
                       │
                    订阅/发布
                       │
                    网关设备

MQTT 运行在 TCP/IP 等能够提供有序、可靠双向字节流的传输之上,并通过发布/订阅模型实现消息解耦。MQTT 3.1.1 是 MQTT 的第一个 OASIS 标准版本,同时标准化为 ISO/IEC 20922:2016;MQTT 5.0 于 2019 年成为 OASIS 标准。


1.2 MQTT 的发展历史

可以简单理解为:

复制代码
1990s
  │
  │ IBM 等推动 MQTT
  ▼
MQTT 3.1
  │
  ▼
MQTT 3.1.1
  │
  │ 2014 OASIS 标准
  ▼
MQTT 5.0
  │
  │ 2019 OASIS 标准
  ▼
现代 IoT / 工业互联网 / 车联网

MQTT 3.1.1

MQTT 3.1.1 是目前大量传统 IoT 系统仍在使用的版本。

特点:

  • 协议简单

  • 客户端实现成本低

  • Broker 支持广泛

  • QoS 0/1/2

  • Retain

  • LWT

  • Clean Session

  • Topic 通配符

  • 用户名/密码

  • TLS


1.3 MQTT 5.0 相比 3.1.1 增加了什么?

MQTT 5.0 不是重新设计了一套 MQTT,而是在 MQTT 3.1.1 核心模型基础上进行了大量增强。

能力 MQTT 3.1.1 MQTT 5.0
发布/订阅
QoS 0/1/2
Retain
LWT
Clean Session 改进
Clean Start
Session Expiry
Reason Code 基础 更丰富
Properties
Message Expiry
Topic Alias
Receive Maximum
Maximum Packet Size
Server Disconnect Reason 较弱
Request/Response 模式支持 需自行设计 更完善
User Properties

MQTT 5.0 的设计目标之一就是增强大型系统扩展性、错误报告、请求/响应模式、扩展机制以及小型客户端支持。

实际开发建议

如果你正在开发一个新 IoT 系统:

Broker 和客户端都支持 MQTT 5.0 时,优先考虑 MQTT 5.0。

但如果需要兼容大量已有设备:

MQTT 3.1.1 仍然是非常重要的兼容版本。


2. 核心概念解析

2.1 Broker

Broker(消息代理) 是 MQTT 系统的核心服务器。

它负责:

  1. 接收客户端连接

  2. 维护客户端连接状态

  3. 接收 PUBLISH 消息

  4. 管理订阅关系

  5. 根据 Topic 匹配订阅者

  6. 转发消息

  7. 处理 QoS

  8. 管理 Retained Message

  9. 管理离线会话

  10. 处理 LWT

  11. 执行认证和 ACL

例如:

复制代码
设备:
client_id = device-001

发布:
iot/device/device-001/telemetry

消息:
{
    "temperature": 26.5
}

Broker 收到后,会查找哪些客户端订阅了:

复制代码
iot/device/device-001/telemetry

然后把消息转发给这些客户端。


2.2 Client

Client(客户端) 是连接 MQTT Broker 的任何程序或设备。

例如:

  • STM32

  • ESP32

  • RK3399 工控机

  • Linux 网关

  • Windows 程序

  • Java 后端

  • Python 服务

  • Go 服务

  • 手机 App

  • Web 前端

因此:

MQTT Client 不等于"设备"。

服务器程序同样可以是 MQTT Client。

例如:

复制代码
传感器 ─────► Broker ◄───── Python程序
                 ▲
                 │
                 └──────── Go程序

2.3 Connection

Connection 指 Client 与 Broker 之间建立的网络连接。

通常:

复制代码
Client
  │
  │ TCP
  ▼
Broker

常见情况下 MQTT 建立在 TCP 上:

复制代码
应用层:MQTT
传输层:TCP
网络层:IP

如果使用 TLS:

复制代码
应用层:MQTT
安全层:TLS
传输层:TCP
网络层:IP

2.4 发布/订阅模型

MQTT 最大的特点之一就是:

Publish / Subscribe(发布/订阅)

传统 TCP 通信:

复制代码
设备 A ───────────► 服务器 B

设备 A 必须知道服务器 B。

MQTT:

复制代码
设备 A ──Publish──► Broker
                       │
                       ├──► 服务器 B
                       ├──► 手机 C
                       └──► 网关 D

设备 A 不需要知道 B、C、D 的 IP 地址。

它只需要:

复制代码
publish(topic, payload)

这就是 MQTT 的解耦


2.5 Topic

Topic 是 MQTT 消息的逻辑地址。

例如:

复制代码
device/001/temperature

可以理解为:

复制代码
device
 └── 001
      └── temperature

再比如:

复制代码
iot/device/001/status
iot/device/001/telemetry
iot/device/001/command
iot/device/001/config

2.6 Payload

Payload 是消息实际携带的数据。

例如:

复制代码
Topic:
iot/device/001/temperature

Payload:
26.5

或者:

复制代码
{
  "deviceId": "001",
  "temperature": 26.5,
  "humidity": 68.2,
  "timestamp": 1787550000
}

MQTT 本身不规定 Payload 的业务格式

Payload 可以是:

  • JSON

  • XML

  • 二进制

  • Protobuf

  • MessagePack

  • 自定义协议

这也是 MQTT 灵活的重要原因之一。


3. MQTT 工作原理与网络模型

3.1 MQTT 通信整体流程

典型流程:

复制代码
Client
  │
  │ TCP Connect
  ▼
Broker
  │
  │ CONNECT
  ▼
Broker
  │
  │ CONNACK
  ▼
Client
  │
  ├── SUBSCRIBE
  │
  ├── PUBLISH
  │
  ├── PINGREQ
  │
  └── DISCONNECT

3.2 CONNECT / CONNACK

MQTT 客户端建立 TCP 连接后,需要发送:

复制代码
CONNECT

Broker 返回:

复制代码
CONNACK

MQTT 3.1.1 的 CONNECT 中通常包含:

复制代码
Protocol Name
Protocol Level
Connect Flags
Keep Alive
Client ID
Will
Username
Password

例如:

复制代码
Client
  │
  │ CONNECT
  │ ClientID=device-001
  │ KeepAlive=60
  │ Username=device001
  ▼
Broker
  │
  │ CONNACK
  │ Return Code = 0
  ▼
Client

MQTT 3.1.1 CONNACK 返回码:

返回码 含义
0 Connection Accepted
1 不支持的协议版本
2 Client Identifier 被拒绝
3 Broker 不可用
4 用户名或密码错误
5 未授权

3.3 Keep Alive

Keep Alive 是 MQTT 非常重要的机制。

例如:

复制代码
Keep Alive = 60 秒

表示客户端和 Broker 约定:

在正常通信情况下,不能长时间没有 MQTT 控制报文。

如果客户端在这个时间内没有其他 MQTT 报文发送,就发送:

复制代码
PINGREQ

Broker 返回:

复制代码
PINGRESP

流程:

复制代码
sequenceDiagram
    participant C as Client
    participant B as Broker

    C->>B: CONNECT
    B-->>C: CONNACK

    Note over C,B: 一段时间没有 MQTT 报文

    C->>B: PINGREQ
    B-->>C: PINGRESP

    Note over C,B: 继续保持连接

MQTT 3.1.1 中,如果 Broker 在规定时间内没有收到客户端的 MQTT 控制报文,它可以判断连接失效;规范中的超时判断涉及 1.5 × Keep Alive

Keep Alive 应该设置多大?

不要简单认为越小越好。

例如:

复制代码
10 秒

优点:

  • 断线检测快

缺点:

  • 心跳频繁

  • 蜂窝网络耗电

  • 增加网络流量

工业设备常见思路:

复制代码
30~120 秒

具体应该根据:

  • 网络类型

  • 设备功耗

  • 断线检测要求

  • NAT 超时时间

  • Broker 负载

综合确定。


3.4 正常断开

客户端主动断开:

复制代码
Client
  │
  │ DISCONNECT
  ▼
Broker

Broker 收到 DISCONNECT 后知道:

这是一次正常断开。

因此通常不会触发 LWT


3.5 异常断开

例如:

复制代码
设备突然断电

或者:

复制代码
4G 网络突然断开

或者:

复制代码
TCP 连接异常中断

客户端没有机会发送:

复制代码
DISCONNECT

Broker 最终发现:

复制代码
连接已经异常消失

此时可以发布 LWT。


4. 报文结构与 QoS

4.1 MQTT 控制报文

MQTT 控制报文基本结构:

复制代码
┌──────────────────────┐
│ Fixed Header         │
├──────────────────────┤
│ Variable Header      │
├──────────────────────┤
│ Payload              │
└──────────────────────┘

Fixed Header

所有 MQTT 控制报文都有固定头。

例如:

复制代码
PUBLISH
SUBSCRIBE
PUBACK
CONNECT
CONNACK
PINGREQ
PINGRESP
DISCONNECT

固定头包含:

复制代码
Message Type
Flags
Remaining Length

4.2 QoS 是什么?

QoS:

Quality of Service,服务质量

MQTT 定义三个等级:

复制代码
QoS 0
QoS 1
QoS 2

可以简单理解:

QoS 含义 是否可能丢失 是否可能重复 开销
0 最多一次 一般不会 最低
1 至少一次 正常协议流程下尽量避免
2 恰好一次 正常协议流程下避免 协议层避免重复交付 最高

注意:

MQTT 的 QoS 不能直接等同于"业务一定只执行一次"。

例如:

复制代码
QoS 2

可以保证 MQTT 协议层的消息交付语义,但如果你的业务程序:

复制代码
收到消息
↓
执行数据库 INSERT
↓
程序崩溃
↓
重新处理

仍然需要业务层设计幂等机制。


4.3 QoS 0:最多一次

QoS 0:

At Most Once

也就是:

发一次,不等待确认。

流程:

复制代码
sequenceDiagram
    participant C as Client
    participant B as Broker

    C->>B: PUBLISH QoS 0
    Note over C,B: 无 PUBACK

特点:

  • 只有一次 PUBLISH

  • 没有 ACK

  • 网络断开可能丢失

  • 开销最低

  • 性能最高

适合:

复制代码
实时温度
实时湿度
CPU使用率
GPS位置
实时电流
实时电压

例如:

复制代码
每秒上报一次温度

26.1
26.2
26.3
26.4
26.5

丢一个:

复制代码
26.3

通常没关系。


4.4 QoS 1:至少一次

QoS 1:

At Least Once

发送:

复制代码
PUBLISH

Broker 返回:

复制代码
PUBACK

流程:

复制代码
sequenceDiagram
    participant C as Client
    participant B as Broker

    C->>B: PUBLISH QoS 1
    B-->>C: PUBACK

如果客户端没有收到 PUBACK:

复制代码
Client
  │
  │ PUBLISH
  ▼
Broker
  X
  │
  │ PUBACK 丢失
  X
Client

客户端可能再次发送:

复制代码
PUBLISH

于是 Broker 可能收到两次。

所以:

QoS 1 允许重复。


4.5 QoS 1 为什么叫"至少一次"?

假设:

复制代码
发送 PUBLISH

Broker 已经成功接收。

但:

复制代码
PUBACK

在网络中丢失。

客户端认为:

复制代码
没有成功

于是重新发送:

复制代码
PUBLISH

Broker 可能再次收到。

所以:

复制代码
消息至少到达一次

但:

复制代码
可能到达多次

因此:

QoS 1 + 业务幂等 = IoT 系统中非常常见的可靠消息方案。


4.6 QoS 2:恰好一次

QoS 2:

Exactly Once

MQTT 3.1.1 使用四步握手:

复制代码
PUBLISH
PUBREC
PUBREL
PUBCOMP

完整流程:

复制代码
sequenceDiagram
    participant C as Client
    participant B as Broker

    C->>B: PUBLISH QoS 2
    B-->>C: PUBREC

    C->>B: PUBREL
    B-->>C: PUBCOMP

    Note over C,B: QoS 2 交互完成

4.7 QoS 2 为什么需要四次交互?

可以把它理解成一个状态机。

第一步:PUBLISH

客户端说:

"我有一条 QoS 2 消息。"

复制代码
C ─── PUBLISH ───► B

第二步:PUBREC

Broker:

"我已经收到。"

复制代码
C ◄── PUBREC ─── B

第三步:PUBREL

Client:

"好的,现在可以释放/继续处理这条消息了。"

复制代码
C ─── PUBREL ───► B

第四步:PUBCOMP

Broker:

"整个 QoS 2 流程完成。"

复制代码
C ◄── PUBCOMP ─── B

因此:

复制代码
PUBLISH
   ↓
PUBREC
   ↓
PUBREL
   ↓
PUBCOMP

4.8 三种 QoS 如何选择?

一个非常实用的判断方法:

QoS 0

问自己:

"这条消息丢了没关系吗?"

如果:

复制代码

选择:

复制代码
QoS 0

QoS 1

问自己:

"消息不能轻易丢,但重复可以通过业务幂等解决吗?"

如果:

复制代码

选择:

复制代码
QoS 1

这是绝大多数 IoT 业务非常实用的选择。


QoS 2

问自己:

"业务确实需要 MQTT 层的 Exactly Once 交付语义,而且能够接受更高协议开销吗?"

如果:

复制代码

再考虑:

复制代码
QoS 2

不要因为:

"QoS 2 最可靠"

就把所有消息都设置成 QoS 2。


4.9 QoS 的一个重要规则

QoS 不是简单地说:

复制代码
客户端设置 QoS 2
=
Broker 一定按照 QoS 2 给订阅者

发布者和订阅者之间的 QoS 需要结合实际订阅 QoS 和 Broker 的转发规则理解。

实际工程中可以简单记住:

发布端 QoS 与订阅端 QoS 都需要合理设计。


5. Topic 与通配符

5.1 Topic 的层级结构

Topic 使用:

复制代码
/

作为层级分隔符。

例如:

复制代码
iot/device/001/temperature

可以设计成:

复制代码
iot
 └── device
      └── 001
           └── temperature

推荐按照:

复制代码
产品 / 设备 / 数据类型

设计。

例如:

复制代码
iot/device/001/telemetry
iot/device/001/status
iot/device/001/event
iot/device/001/command
iot/device/001/config

5.2 推荐 Topic 设计

例如充电桩系统:

复制代码
charger/{chargerId}/telemetry
charger/{chargerId}/status
charger/{chargerId}/event
charger/{chargerId}/command
charger/{chargerId}/config

例如:

复制代码
charger/CHG001/telemetry
charger/CHG001/status
charger/CHG001/event
charger/CHG001/command
charger/CHG001/config

这种设计比:

复制代码
temperature

更适合大型 IoT 系统。


5.3 单级通配符 +

+ 只能匹配一个层级

例如:

复制代码
charger/+/status

可以匹配:

复制代码
charger/001/status
charger/002/status
charger/ABC/status

但是不能匹配:

复制代码
charger/001/device/status

因为:

复制代码
+

只代表一个 Topic Level。


5.4 多级通配符 #

# 可以匹配多个层级。

例如:

复制代码
charger/#

可以匹配:

复制代码
charger/001/status
charger/001/telemetry
charger/001/event
charger/002/status
charger/002/telemetry

甚至:

复制代码
charger

及其后续层级。


5.5 通配符使用规则

+ 必须占据完整一级

正确:

复制代码
charger/+/status

错误:

复制代码
charger/+001/status

错误:

复制代码
charger/001+/status

# 必须位于末尾

正确:

复制代码
charger/#

正确:

复制代码
charger/001/#

错误:

复制代码
charger/#/status

5.6 通配符性能问题

千万不要让大量业务客户端无脑订阅:

复制代码
#

或者:

复制代码
iot/#

原因是 Broker 需要:

复制代码
消息
 ↓
Topic匹配
 ↓
大量订阅关系
 ↓
大量客户端转发

如果:

复制代码
10000个客户端

都订阅:

复制代码
#

那么系统消息分发压力可能非常大。

生产环境应当:

按照业务边界精确订阅 Topic。


5.7 $SYS

很多 Broker 提供:

复制代码
$SYS/...

用于系统状态监控。

例如:

复制代码
$SYS/#

可能可以查看:

  • Broker 状态

  • 客户端数量

  • 消息数量

  • 网络流量

  • Topic 状态

但是:

$SYS 不是所有 Broker 都保证完全一致。

具体 Topic 结构需要查看对应 Broker 文档。


6. 高级特性

6.1 遗嘱消息 LWT

LWT:

Last Will and Testament

中文通常称:

遗嘱消息

它解决的问题是:

设备异常断开后,如何通知其他系统?


6.1.1 为什么需要 LWT?

假设设备:

复制代码
device-001

正常运行:

复制代码
status = online

突然:

复制代码
设备断电

设备没有机会发送:

复制代码
status = offline

Broker 可以在客户端连接时提前保存一条 Will Message:

复制代码
Topic:
device/001/status

Payload:
offline

如果客户端异常断开:

复制代码
Broker
  │
  │ 检测异常断开
  ▼
发布 Will Message

6.1.2 LWT 流程

复制代码
sequenceDiagram
    participant D as Device
    participant B as Broker
    participant S as Server

    D->>B: CONNECT + Will Message
    B-->>D: CONNACK

    D->>B: PUBLISH status=online
    B->>S: status=online

    Note over D: 设备突然断电

    B->>B: 检测 TCP/KeepAlive 异常
    B->>S: PUBLISH status=offline

6.1.3 LWT 最典型应用

复制代码
device/{id}/status

在线:

复制代码
{
  "status": "online"
}

离线:

复制代码
{
  "status": "offline"
}

这样后台系统就可以实时维护:

复制代码
设备在线列表
设备离线列表
设备最后在线时间

6.2 Retained Message

Retain 是 MQTT 中另一个非常重要的机制。

普通消息:

复制代码
Client A
   │
   │ PUBLISH
   ▼
Broker
   │
   └──► 当前在线订阅者

如果 Client B 后来才订阅:

复制代码
Client B
   │
   │ SUBSCRIBE
   ▼
Broker

默认情况下:

B 不会收到以前已经发送的普通消息。

但是如果发布消息时设置:

复制代码
retain = true

Broker 可以保存该 Topic 的最后一条 retained message。

例如:

复制代码
Topic:
device/001/status

Payload:
online

retain=true

以后新客户端订阅:

复制代码
device/001/status

Broker 可以立即把:

复制代码
online

发送给它。


6.2.1 Retain 的典型应用

非常适合:

复制代码
设备当前状态
设备当前配置
设备当前模式
设备在线状态

例如:

复制代码
device/001/status = online
device/001/mode = auto
device/001/config = {...}

6.3 Retain 与普通消息的区别

类型 Broker保存 新订阅者能收到旧状态
普通消息
Retained Message

因此:

Retain 更适合"当前状态",不适合充当完整历史消息队列。

例如:

复制代码
temperature=26
temperature=27
temperature=28

如果全部 retain:

最终通常只需要保存当前状态 28。

如果需要历史数据:

复制代码
MQTT
 ↓
Kafka / Redis / MySQL / 时序数据库

应该由业务系统持久化。


6.4 Clean Session / Clean Start

这是 MQTT 会话机制中最容易混淆的部分之一。

MQTT 3.1.1

使用:

复制代码
Clean Session

例如:

复制代码
Clean Session = true

表示:

不需要持久化该客户端的 MQTT 会话状态。

如果:

复制代码
Clean Session = false

Broker 可以维护持久会话。

包括:

  • 订阅关系

  • QoS 1/2 未完成消息

  • 离线期间符合条件的消息


6.5 MQTT 5.0

MQTT 5.0 把这一机制拆得更加清晰:

复制代码
Clean Start
+
Session Expiry Interval

可以理解成:

复制代码
Clean Start
    ↓
这次连接是否重新开始会话?

Session Expiry
    ↓
断开后会话保留多久?

例如:

复制代码
Clean Start = false
Session Expiry = 3600

表示:

断线后会话可以保留 3600 秒。


6.6 离线消息

例如:

复制代码
设备
  │
  │ 断网
  X
Broker

设备断线期间:

复制代码
服务器 ──► command

如果对应会话满足持久化条件,Broker 可以保存符合规则的 QoS 消息。

设备重新连接:

复制代码
设备
  │
  │ CONNECT
  ▼
Broker
  │
  └──► 离线期间消息

实际使用时要注意:

离线消息不是"Broker 永久保存所有消息"。

它受到:

  • Session

  • QoS

  • Broker 配置

  • Session Expiry

  • Message Expiry

  • 队列限制

等因素影响。


7. 安全与认证机制

7.1 MQTT 本身不等于安全

非常重要:

复制代码
MQTT

本身并不自动提供:

复制代码
加密
身份认证
权限控制

生产环境不能简单地:

复制代码
互联网
   ↓
1883
   ↓
MQTT Broker

然后认为系统安全。


7.2 TLS

推荐:

复制代码
MQTT
 ↓
TLS
 ↓
TCP

常见端口:

复制代码
1883   MQTT/TCP
8883   MQTT/TLS

TLS 可以保护:

复制代码
用户名
密码
Payload
Topic
MQTT控制报文

避免被网络中间人直接窃听。


7.3 单向 TLS

常见方式:

复制代码
Client
  │
  │ TLS
  ▼
Broker

Client 验证 Broker 证书。

适合大多数互联网 IoT 系统。


7.4 双向 TLS / mTLS

更高安全级别:

复制代码
Client
  │
  │ Client Certificate
  ▼
Broker
  │
  │ Broker Certificate
  ▼
Client

双方都验证证书。

特别适合:

  • 工业互联网

  • 车联网

  • 企业 IoT

  • 高安全设备


7.5 用户名密码

最常见的应用层认证:

复制代码
username
password

例如:

复制代码
username=device001
password=********

但是:

用户名密码一定应该配合 TLS 使用。

否则密码可能在网络上被窃听。


7.6 Token

也可以使用:

复制代码
username = device001
password = JWT/Token

或者由 Broker 的认证扩展机制实现 Token/JWT。

典型结构:

复制代码
设备
 │
 │ Token
 ▼
认证服务
 │
 ▼
Broker

7.7 ACL

ACL:

Access Control List

解决:

这个 Client 能访问哪些 Topic?

例如设备:

复制代码
device001

只允许:

复制代码
publish:
device/device001/telemetry

subscribe:
device/device001/command
device/device001/config

禁止:

复制代码
publish:
device/device002/telemetry

也禁止:

复制代码
subscribe:
#

7.8 ACL 设计原则

推荐:

复制代码
一个设备
    ↓
一个身份
    ↓
限定自己的 Topic

例如:

复制代码
device/{deviceId}/telemetry
device/{deviceId}/status
device/{deviceId}/command

而不是:

复制代码
所有设备共享一个账号

否则一台设备泄露账号后:

所有设备都可能受到影响。


8. 实战指南与代码示例

8.1 Python MQTT 客户端

这里使用 Eclipse Paho MQTT Python。

Paho Python 客户端支持 MQTT 3.1、3.1.1 和 5.0,并提供连接、发布、订阅、回调和网络循环等功能。当前文档推荐使用新版 Callback API。

安装:

复制代码
pip install paho-mqtt

8.2 完整 MQTT 3.1.1 示例

下面实现:

  • 连接 Broker

  • 设置 Client ID

  • 设置用户名密码

  • 设置 LWT

  • 订阅 Topic

  • 发布消息

  • 接收消息

  • 自动网络循环

  • 正常退出

    import json
    import time

    import paho.mqtt.client as mqtt

    =========================

    MQTT Broker 配置

    =========================

    BROKER_HOST = "127.0.0.1"
    BROKER_PORT = 1883

    USERNAME = "iot_user"
    PASSWORD = "iot_password"

    CLIENT_ID = "python-device-001"

    当前设备自己的 Topic

    STATUS_TOPIC = f"device/{CLIENT_ID}/status"
    TELEMETRY_TOPIC = f"device/{CLIENT_ID}/telemetry"
    COMMAND_TOPIC = f"device/{CLIENT_ID}/command"

    =========================

    连接成功回调

    =========================

    def on_connect(client, userdata, flags, reason_code, properties):
    """
    Broker 返回 CONNACK 后调用。

    复制代码
      reason_code == 0 表示连接成功。
      """
    
      print(f"MQTT 连接结果: {reason_code}")
    
      if reason_code != 0:
          print("MQTT Broker 连接失败")
          return
    
      print("MQTT Broker 连接成功")
    
      # 连接成功后订阅命令 Topic。
      #
      # 一个非常重要的实践:
      # 把 subscribe() 放在 on_connect() 中。
      #
      # 原因:
      # 如果网络断开后自动重连,
      # on_connect() 会再次执行,
      # 从而自动恢复订阅。
      client.subscribe(COMMAND_TOPIC, qos=1)
    
      print(f"已订阅: {COMMAND_TOPIC}")

    =========================

    收到消息回调

    =========================

    def on_message(client, userdata, message):
    """
    收到 Broker 转发的消息后调用。
    """

    复制代码
      topic = message.topic
      payload = message.payload.decode("utf-8", errors="replace")
    
      print("=" * 60)
      print(f"收到 Topic: {topic}")
      print(f"QoS: {message.qos}")
      print(f"Retain: {message.retain}")
      print(f"Payload: {payload}")
    
      # 尝试解析 JSON
      try:
          data = json.loads(payload)
    
          print("JSON 数据:")
          print(data)
    
          # 根据业务执行命令
          command = data.get("command")
    
          if command == "restart":
              print("收到重启命令")
    
          elif command == "status":
              print("收到状态查询命令")
    
      except json.JSONDecodeError:
          print("Payload 不是 JSON")

    =========================

    断开连接回调

    =========================

    def on_disconnect(client, userdata, disconnect_flags,
    reason_code, properties):
    """
    MQTT 连接断开时调用。
    """

    复制代码
      print(f"MQTT 连接断开: {reason_code}")

    =========================

    创建 MQTT Client

    =========================

    client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id=CLIENT_ID,
    protocol=mqtt.MQTTv311
    )

    =========================

    用户名密码

    =========================

    client.username_pw_set(
    USERNAME,
    PASSWORD
    )

    =========================

    设置 LWT

    =========================

    client.will_set(
    STATUS_TOPIC,
    payload=json.dumps({
    "status": "offline",
    "timestamp": int(time.time())
    }),
    qos=1,
    retain=True
    )

    =========================

    设置回调函数

    =========================

    client.on_connect = on_connect
    client.on_message = on_message
    client.on_disconnect = on_disconnect

    =========================

    连接 Broker

    =========================

    print("正在连接 MQTT Broker...")

    client.connect(
    BROKER_HOST,
    BROKER_PORT,
    keepalive=60
    )

    =========================

    启动网络循环

    =========================

    client.loop_start()

    try:

    复制代码
      # 发布在线状态
      online_payload = json.dumps({
          "status": "online",
          "timestamp": int(time.time())
      })
    
      client.publish(
          STATUS_TOPIC,
          online_payload,
          qos=1,
          retain=True
      )
    
      print(f"已发布在线状态: {online_payload}")
    
    
      # 持续上报遥测数据
      while True:
    
          telemetry = {
              "deviceId": CLIENT_ID,
              "temperature": 26.5,
              "humidity": 68.2,
              "timestamp": int(time.time())
          }
    
          result = client.publish(
              TELEMETRY_TOPIC,
              json.dumps(telemetry),
              qos=1,
              retain=False
          )
    
          if result.rc == mqtt.MQTT_ERR_SUCCESS:
              print(f"遥测数据发送成功: {telemetry}")
          else:
              print(f"发送失败,错误码: {result.rc}")
    
          time.sleep(10)

    except KeyboardInterrupt:

    复制代码
      print("程序退出")

    finally:

    复制代码
      # 正常退出前主动发送 DISCONNECT
      client.disconnect()
    
      # 停止网络线程
      client.loop_stop()
    
      print("MQTT 客户端已退出")

Paho 文档特别建议把订阅操作放在 on_connect() 中,这样客户端重新连接后可以重新建立订阅。新版 Paho 2.x 的回调 API 使用 CallbackAPIVersion.VERSION2,并统一了 MQTT 3.x/5.x 回调接口。


8.3 代码运行流程

启动:

复制代码
Python
  │
  │ connect()
  ▼
Broker
  │
  │ CONNACK
  ▼
on_connect()
  │
  ├── subscribe()
  │
  └── publish online
       │
       ▼
loop_start()
       │
       ├── 接收消息
       ├── 心跳
       ├── ACK
       └── 自动网络处理

8.4 为什么必须调用 loop_start?

这是很多 MQTT 初学者容易踩的坑。

MQTT 不只是:

复制代码
publish()

后台还需要处理:

  • TCP 网络数据

  • PINGREQ/PINGRESP

  • PUBACK

  • PUBREC

  • PUBREL

  • PUBCOMP

  • SUBACK

  • 重连

  • 回调

Paho 提供:

复制代码
client.loop_start()

让后台线程持续处理 MQTT 网络事件。Paho 同时提供 loop_forever() 和手动 loop() 等网络循环方式。


8.5 Broker 快速部署

这里以 Eclipse Mosquitto 为例。

Eclipse Mosquitto 是一个轻量级开源 MQTT Broker,同时支持 MQTT 5、3.1.1 和 3.1。其官方 Docker 镜像提供 /mosquitto/config/mosquitto/data/mosquitto/log 等目录用于配置、持久化和日志。


8.5.1 创建目录

复制代码
mkdir -p ~/mqtt/mosquitto/config
mkdir -p ~/mqtt/mosquitto/data
mkdir -p ~/mqtt/mosquitto/log

8.5.2 创建配置文件

复制代码
vim ~/mqtt/mosquitto/config/mosquitto.conf

最简单配置:

复制代码
# MQTT TCP
listener 1883

# 允许匿名访问
#
# 仅用于本地测试。
# 生产环境不要这样配置。
allow_anonymous true

# 持久化
persistence true

persistence_location /mosquitto/data/

# 日志
log_dest file /mosquitto/log/mosquitto.log

8.5.3 Docker 启动

复制代码
docker run -d \
  --name mosquitto \
  --restart unless-stopped \
  -p 1883:1883 \
  -v ~/mqtt/mosquitto/config:/mosquitto/config \
  -v ~/mqtt/mosquitto/data:/mosquitto/data \
  -v ~/mqtt/mosquitto/log:/mosquitto/log \
  eclipse-mosquitto:2.1.2-alpine

官方镜像支持 2.1.2-alpinelatest 等标签,并支持包括 amd64arm64 在内的多种架构。生产环境更推荐固定具体版本,而不是长期使用 latest


8.6 验证 Broker

查看:

复制代码
docker ps

查看日志:

复制代码
docker logs mosquitto

或者:

复制代码
tail -f ~/mqtt/mosquitto/log/mosquitto.log

8.7 使用 mosquitto-clients 测试

订阅:

复制代码
mosquitto_sub \
  -h 127.0.0.1 \
  -p 1883 \
  -t 'test/topic'

另一个终端发布:

复制代码
mosquitto_pub \
  -h 127.0.0.1 \
  -p 1883 \
  -t 'test/topic' \
  -m 'hello mqtt'

订阅端应该收到:

复制代码
hello mqtt

8.8 MQTT Broker 的生产环境配置

测试环境可以:

复制代码
allow_anonymous true

生产环境不要这么做。

生产环境应该:

复制代码
TLS
+
认证
+
ACL
+
日志
+
监控
+
持久化

典型架构:

复制代码
Internet
    │
    │ 8883/TLS
    ▼
┌───────────────┐
│ MQTT Broker   │
│               │
│ Authentication│
│ ACL           │
│ TLS           │
│ Monitoring    │
└───────┬───────┘
        │
        ▼
   IoT Backend

8.9 EMQX

如果系统规模较大,除了 Mosquitto,也可以考虑 EMQX。

EMQX 提供:

  • MQTT Broker

  • Dashboard

  • 认证

  • ACL

  • 集群

  • 规则引擎

  • 数据集成

  • WebSocket

  • MQTT over TLS

  • MQTT 5

  • 大规模设备连接能力

EMQX 官方文档提供 Docker 部署方式,并支持将数据和日志目录持久化;当前官方文档也提供单节点和 Docker Compose 集群部署方案。

例如:

复制代码
docker run -d \
  --name emqx \
  --restart unless-stopped \
  -p 1883:1883 \
  -p 8883:8883 \
  -p 8083:8083 \
  -p 8084:8084 \
  -p 18083:18083 \
  emqx/emqx:latest

其中:

复制代码
1883   MQTT TCP
8883   MQTT TLS
8083   MQTT WebSocket
8084   MQTT Secure WebSocket
18083  Dashboard

EMQX 官方 Docker 文档列出了这些常用监听端口。


9. 最佳实践与避坑指南

9.1 Client ID 必须合理设计

错误:

复制代码
device

如果 10000 个设备都使用:

复制代码
device

Broker 会认为它们是同一个 Client ID。

可能出现:

复制代码
设备 A 登录
   ↓
device

设备 B 登录
   ↓
device

A 被踢下线

推荐设计

例如:

复制代码
device-{product}-{deviceId}

实际:

复制代码
charger-001-CHG000001
charger-001-CHG000002
sensor-001-SN000001

Client ID 应当:

  • 全局尽量唯一

  • 稳定

  • 可追踪

  • 不要频繁随机变化

  • 不要包含敏感信息


9.2 不要把大文件塞进 MQTT

例如:

复制代码
1 MB 图片
20 MB 视频
100 MB 固件

直接 MQTT:

复制代码
PUBLISH
Payload = 100MB

通常不是好设计。

更推荐:

复制代码
设备
 │
 │ MQTT
 │
 ├──► 控制消息
 │
 └──► 文件URL
          │
          ▼
       HTTP/HTTPS

例如 MQTT Payload:

复制代码
{
  "type": "firmware",
  "version": "2.1.5",
  "url": "https://example.com/firmware/2.1.5.bin",
  "sha256": "..."
}

设备收到后:

复制代码
MQTT
 ↓
获取下载地址
 ↓
HTTPS下载
 ↓
SHA256校验
 ↓
升级

这比 MQTT 直接传大文件更加合理。


9.3 如果必须传大消息

可以采用:

复制代码
分片

例如:

复制代码
file/001/chunk/0001
file/001/chunk/0002
file/001/chunk/0003
...

但是需要自行处理:

  • 分片编号

  • 总分片数

  • 顺序

  • 丢包

  • 重传

  • 校验

  • 超时

  • 文件组装

因此:

能用 HTTP/HTTPS 下载文件,就尽量不要使用 MQTT 传大文件。


9.4 弱网环境优化

物联网设备经常面对:

复制代码
4G
5G
Wi-Fi
NB-IoT
卫星网络
工业无线网络

网络可能:

复制代码
高延迟
丢包
抖动
频繁断线
NAT
IP变化

建议:

1. 合理 Keep Alive

不要:

复制代码
KeepAlive = 5秒

除非确实需要。


2. 自动重连

采用:

复制代码
指数退避

例如:

复制代码
1秒
2秒
4秒
8秒
16秒
30秒
60秒

避免所有设备同时上线造成:

惊群效应 / Thundering Herd


3. QoS 选择合理

实时遥测:

复制代码
QoS 0

重要业务:

复制代码
QoS 1

极少数严格场景:

复制代码
QoS 2

4. 消息不要过大

尽量:

复制代码
几百字节
几KB

而不是:

复制代码
几MB
几十MB

9.5 消息幂等

这是 MQTT 项目中非常重要的一点。

例如:

复制代码
QoS 1

可能重复收到:

复制代码
messageId = 10001

第一次:

复制代码
执行开门

第二次:

复制代码
再次执行开门

如果业务不能重复执行,就需要:

复制代码
消息ID
+
幂等表
+
业务状态机

例如:

复制代码
{
  "messageId": "MSG202608240001",
  "deviceId": "001",
  "command": "open"
}

数据库:

复制代码
messageId
    ↓
是否处理过?
    │
    ├── 是 → 忽略
    │
    └── 否 → 执行业务

9.6 MQTT 不等于业务可靠性

必须建立一个正确认识:

复制代码
MQTT QoS

解决的是:

MQTT 消息传输层面的交付语义。

它不能自动解决:

复制代码
数据库事务
业务幂等
订单状态
支付一致性
设备执行成功
业务最终一致性

例如:

复制代码
MQTT QoS 1
      ↓
Broker
      ↓
后端
      ↓
数据库

后端收到消息不代表:

复制代码
数据库一定成功

因此大型系统应该设计:

复制代码
MQTT
 ↓
消息接收服务
 ↓
消息队列 / 数据库
 ↓
业务处理
 ↓
状态确认

9.7 消息丢失排查方法

遇到:

"设备明明发送了,但是服务器没收到。"

不要直接认为是 MQTT 丢消息。

按照下面顺序排查:

复制代码
设备
 ↓
TCP
 ↓
Broker
 ↓
Topic
 ↓
订阅关系
 ↓
QoS
 ↓
Session
 ↓
ACL
 ↓
后端程序

第一步:设备是否真的连接成功?

检查:

复制代码
CONNACK

第二步:Topic 是否正确?

例如设备发送:

复制代码
device/001/telemetry

后端订阅:

复制代码
device/001/status

当然收不到。


第三步:QoS 是否合理?

例如:

复制代码
QoS 0

网络断开时消息可能丢失。


第四步:是否订阅成功?

检查:

复制代码
SUBSCRIBE
SUBACK

第五步:是否受到 ACL 限制?

例如:

复制代码
Client 001

只允许:

复制代码
device/001/#

却订阅:

复制代码
device/002/#

可能被拒绝。


9.8 重复消费排查

如果服务器发现:

复制代码
同一条消息处理了两次

首先检查:

复制代码
QoS 1

然后检查:

复制代码
Client重连
PUBACK丢失
消息重传

之后再检查业务代码:

复制代码
是否幂等?

不要简单地说:

MQTT 出 Bug 了。


9.9 连接被拒绝排查

MQTT 3.1.1:

CONNACK 含义 常见原因
0 成功 正常
1 协议版本不支持 Client/Broker版本不兼容
2 Client ID 被拒 ID异常或Broker策略
3 Server不可用 Broker状态异常
4 用户名密码错误 认证失败
5 未授权 ACL/认证策略

例如:

复制代码
Connection refused: not authorised

优先检查:

复制代码
用户名
密码
认证插件
ACL
Client ID

Paho 的官方 API 文档也列出了 MQTT 3.x 常见 CONNACK 拒绝原因,包括协议版本、Client ID、服务器不可用、用户名密码以及未授权等。


9.10 生产环境推荐架构

一个比较典型的 IoT 架构:

复制代码
flowchart LR

    A[IoT设备] -->|MQTT/TLS| B[MQTT Broker]

    B --> C[设备接入服务]

    C --> D[业务服务]

    C --> E[Kafka]

    E --> F[数据处理]

    F --> G[MySQL]

    F --> H[时序数据库]

    D --> I[API服务]

    I --> J[Web/App]

    B --> K[监控系统]

职责可以划分为:

复制代码
MQTT Broker
    ↓
负责连接与消息传输

IoT 接入服务
    ↓
负责协议适配、设备身份、消息解析

Kafka
    ↓
负责大规模异步消息流

MySQL
    ↓
负责业务数据

时序数据库
    ↓
负责设备遥测数据

Redis
    ↓
负责实时状态/缓存

Web/App
    ↓
负责业务展示

10. 附录

10.1 常见 MQTT 端口

端口 常见用途
1883 MQTT TCP
8883 MQTT over TLS
8083 MQTT over WebSocket
8084 MQTT over Secure WebSocket
18083 EMQX Dashboard

注意:

端口不是 MQTT 标准强制规定的业务端口。

例如 MQTT 服务器完全可以监听:

复制代码
31883
38883

实际端口由 Broker 配置决定。


10.2 MQTT over WebSocket

MQTT 不只可以运行在传统 TCP 上。

浏览器无法像普通 MQTT Client 那样直接建立任意 TCP Socket,因此 Web 应用通常使用:

复制代码
MQTT over WebSocket

结构:

复制代码
Browser
   │
   │ WebSocket
   ▼
MQTT Broker

例如:

复制代码
ws://server:8083/mqtt

安全环境:

复制代码
wss://server:8084/mqtt

这对于:

复制代码
Vue
React
Web管理平台
浏览器监控页面

非常有用。


10.3 MQTT 控制报文速查

报文 方向 作用
CONNECT Client → Broker 建立 MQTT 会话
CONNACK Broker → Client CONNECT 响应
PUBLISH 双向 发布消息
PUBACK 双向 QoS 1 确认
PUBREC 双向 QoS 2 第一步确认
PUBREL 双向 QoS 2 释放
PUBCOMP 双向 QoS 2 完成
SUBSCRIBE Client → Broker 订阅
SUBACK Broker → Client 订阅确认
UNSUBSCRIBE Client → Broker 取消订阅
UNSUBACK Broker → Client 取消订阅确认
PINGREQ Client → Broker 心跳
PINGRESP Broker → Client 心跳响应
DISCONNECT 双向语义中的主动断开报文 正常断开

10.4 MQTT QoS 速查

复制代码
QoS 0

PUBLISH
   ↓
完成

QoS 1

PUBLISH
   ↓
PUBACK
   ↓
完成

QoS 2

PUBLISH
   ↓
PUBREC
   ↓
PUBREL
   ↓
PUBCOMP
   ↓
完成

记忆口诀:

0 不确认,1 一次确认,2 四步握手。


10.5 MQTT 核心机制速查

机制 解决什么问题
QoS 0 低开销实时消息
QoS 1 重要消息可靠传输
QoS 2 MQTT 层 Exactly Once 交付语义
Keep Alive 检测连接状态
LWT 异常离线通知
Retain 保存最新状态
Session 保存客户端会话
Clean Session MQTT 3.1.1 会话生命周期
Clean Start MQTT 5.0 新会话控制
Session Expiry MQTT 5.0 会话保留时间
ACL Topic 权限控制
TLS 网络加密
Username/Password 身份认证
Token 动态身份认证

10.6 MQTT 3.1.1 与 MQTT 5.0 实战选择建议

如果是:

老设备接入

复制代码
MQTT 3.1.1

通常更容易兼容。

新 IoT 平台

复制代码
MQTT 5.0

更推荐。

大规模平台

建议重点考虑 MQTT 5.0 的:

复制代码
Reason Code
Properties
Session Expiry
Message Expiry
Receive Maximum
Topic Alias
Maximum Packet Size
Request/Response
User Properties

10.7 推荐学习路线

建议不要一上来就研究 MQTT 报文二进制编码。

推荐按下面顺序学习:

复制代码
第一阶段
MQTT是什么
    ↓
Client / Broker
    ↓
Publish / Subscribe
    ↓
Topic

第二阶段
CONNECT / CONNACK
    ↓
SUBSCRIBE / SUBACK
    ↓
PUBLISH
    ↓
PINGREQ / PINGRESP
    ↓
DISCONNECT

第三阶段
QoS 0
    ↓
QoS 1
    ↓
QoS 2

第四阶段
LWT
    ↓
Retain
    ↓
Session

第五阶段
TLS
    ↓
认证
    ↓
ACL

第六阶段
Broker集群
    ↓
消息持久化
    ↓
Kafka
    ↓
规则引擎
    ↓
监控
    ↓
IoT平台架构

10.8 官方标准与学习资源

MQTT 3.1.1 官方规范

OASIS MQTT Version 3.1.1 Specification

MQTT 3.1.1 是学习 MQTT 协议细节最重要的规范之一。

MQTT 5.0 官方规范

OASIS MQTT Version 5.0 Specification

MQTT 5.0 在 3.1.1 基础上增加了大量可扩展性、错误处理、会话和消息属性等能力。

Eclipse Paho Python

Eclipse Paho MQTT Python Documentation

适合使用 Python 快速编写 MQTT Client。

Eclipse Mosquitto

Eclipse Mosquitto 官方 Docker 镜像

适合:

  • 本地开发

  • MQTT 协议学习

  • 测试环境

  • 小型 IoT 项目

  • 嵌入式环境

官方镜像同时支持 MQTT 5、3.1.1 和 3.1。

EMQX

EMQX 官方文档

适合进一步学习:

  • 大规模设备接入

  • MQTT 集群

  • 认证

  • ACL

  • 规则引擎

  • 数据集成

  • Dashboard

  • 企业级 IoT 平台


11. 总结

MQTT 真正值得掌握的不是几个 API,而是下面这套完整模型:

复制代码
                         ┌───────────────┐
                         │ MQTT Broker   │
                         │               │
                         │ Topic路由     │
                         │ QoS           │
                         │ Session       │
                         │ Retain        │
                         │ LWT           │
                         │ ACL           │
                         └───────┬───────┘
                                 │
               ┌─────────────────┼─────────────────┐
               │                 │                 │
               ▼                 ▼                 ▼
            IoT设备            网关              后端服务
               │                 │                 │
               └────── Publish / Subscribe ───────┘

从开发角度看,可以把 MQTT 总结为:

复制代码
MQTT
│
├── Client
│
├── Broker
│
├── Connection
│
├── Topic
│
├── Payload
│
├── Publish / Subscribe
│
├── QoS
│   ├── QoS 0
│   ├── QoS 1
│   └── QoS 2
│
├── Keep Alive
│
├── LWT
│
├── Retain
│
├── Session
│
├── TLS
│
└── ACL

如果进一步做生产级 IoT 系统,则应该继续向:

复制代码
MQTT
 ↓
Broker集群
 ↓
设备认证
 ↓
ACL
 ↓
IoT接入层
 ↓
Kafka
 ↓
业务服务
 ↓
MySQL / Redis / 时序数据库
 ↓
监控与告警

演进。

最终需要建立一个核心认识:

MQTT 解决的是"设备与系统之间如何高效、可靠地传输消息",而完整的 IoT 平台还需要解决设备身份、权限、安全、数据持久化、消息幂等、业务状态机、设备影子、OTA、监控和故障恢复等问题。

掌握 Topic + QoS + Session + LWT + Retain + TLS + ACL 这七个核心机制,基本就掌握了 MQTT 工程实践中最重要的部分。

相关推荐
那年窗外下的雪.1 小时前
AIDC 学习日志|第 14 天|MAC Flapping 与 EVPN 多归属分层排障
网络·git·学习·macos·github·spine
2601_963282771 小时前
油田、工矿厂区 DMR 专网对讲系统:部署实施、干扰治理与防爆终端选型实战
网络·架构
随风而飘1862 小时前
Keithley美国吉时利 2016-P 6.5位音频分析数字多用表
网络·人工智能·功能测试
聚铭网络2 小时前
【一周安全资讯】《网络数据安全风险评估办法》正式实施;隐私加密通讯平台Threema遭DDoS攻击,服务大面积瘫痪
网络·安全·ddos
xiaoxiangsiyan2 小时前
现代大型虚拟化智慧园区整体架构EVPN‑VXLAN落地全解
运维·网络·学习·架构
方芯半导体2 小时前
EtherCAT 从站方案中 MAC、PHY 与 MII 的层级关系与实现解析
网络·人工智能·单片机·macos·ethercat
limingade2 小时前
智能拨号器APP-外部WebSocket话单推送系统接口文档
网络·websocket·智能拨号器app·远程推送号码到手机拨打电话·手机app稳定长连接·手机拨打电话后推送话单和录音
南京码讯光电技术有限公司3 小时前
油气井场防爆5G路由器如何选型
网络·5g·智能路由器
见山是山-见水是水3 小时前
围绕HTTP 网络请求实战构建原生体验:设计取舍、实现与排错
网络·网络协议·http·华为·harmonyos