📖 从入门到上手

MQTT 知识小册子

这份 Wiki 写给刚接触 MQTT 的你:协议怎么工作、主题怎么设计、QoS 怎么选、消息体怎么组织,以及怎么把设备连上 mqttyyc.top。一共 15 章,前半讲概念,后半全是能直接抄的代码,第 13 章更是四套改个凭据就能跑的完整模板。

1

MQTT 是什么

MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息协议,诞生于 1999 年,如今是物联网通信的事实标准(OASIS 标准,主流版本为 MQTT 3.1.1 和 5.0)。

📢 发布(Publish)

设备(发布者)把消息发到某个主题上,发完就走,不需要知道谁在收。

📬 订阅(Subscribe)

应用(订阅者)向 Broker 登记感兴趣的主题,一有消息到来,Broker 立刻推送。

中间人叫 Broker(消息代理服务器)——本站的 mqttyyc.top 就是一个公共 Broker(由 EMQX 提供)。发布者和订阅者互不认识,都只跟 Broker 打交道,这种"解耦"让设备端可以做得非常轻:

  • 协议头极小:固定头部只有 2 字节,适合低带宽、不稳定网络;
  • 基于 TCP:长连接 + 心跳(Keep Alive),弱网下也能保持在线;
  • 三种角色解耦:时间解耦(双方不用同时在线处理)、空间解耦(不用知道对方地址)、同步解耦(收发互不阻塞)。
一句话理解:MQTT 就像"广播电台 + 订阅频道"。设备往频道(主题)里播消息,谁订阅了这个频道,Broker 就把消息推给谁。

哪些场景特别适合 MQTT?

  • 传感器遥测上报:温湿度、电量、位置等周期数据;
  • 设备远程控制:手机/网页下发开关、亮度等指令;
  • 状态实时同步:设备在线/离线、任务进度广播;
  • 弱网 / 低功耗环境:4G Cat.1、NB-IoT、电池供电设备。
2

对比 HTTP 与版本选择

常有人问:都已经有 HTTP 了,为什么还要 MQTT?两者定位不同——HTTP 是"一问一答",MQTT 是"常驻在线的推送"。

对比项HTTPMQTT
通信模式请求-响应,客户端主动拉取发布-订阅,消息到达即时推送
连接形态短连接为主,用完即断长连接 + 心跳保活
协议开销头部几百字节起步固定头最小仅 2 字节
离线处理无(请求失败就失败)QoS 1/2 可缓存补发、遗嘱通知
适合网页、API 查询、文件传输设备遥测、指令下发、状态同步

举个例子:设备每秒上报一次温度,用 HTTP 就得每秒建连、带一堆请求头;用 MQTT 一条长连接常驻,一条消息只有几十字节开销,弱网下还能靠 QoS 补发。

连 3.1.1 还是 5.0?

  • MQTT 3.1.1:兼容性最好,几乎所有库和老设备都支持,本站完全支持;
  • MQTT 5.0:本站同样支持,新增连接失败原因码、消息属性(User Properties)、消息过期时间、共享订阅等能力,排查问题和做服务消费更方便;
  • 怎么选:你用的库两者都支持就选 5.0;老固件、老库保持 3.1.1 完全没问题,两者可以互通。
共享订阅一瞥:多个后端实例可以用 $share/组名/主题 订阅同一主题,Broker 会把消息轮流分发给组内成员,实现负载均衡消费——这是做"MQTT 转存数据库"之类服务时的常用技巧。
3

主题与通配符

主题(Topic)就是消息的"频道名",本质是一个 UTF-8 字符串,用 / 分层。发布时用精确主题,订阅时可以用通配符。

主题设计建议

主题命名参考
# 推荐格式:项目前缀 / 设备或分类 / 数据项
myhome/livingroom/temp        # 客厅温度
myhome/livingroom/humidity    # 客厅湿度
farm/greenhouse-01/soil       # 大棚 1 号土壤湿度
device/esp32-a1b2/status      # 设备在线状态

✅ 好的主题

myhome/livingroom/temp——有自己的前缀、层级清晰、全小写,别人不容易撞车也不容易误读。

❌ 不好的主题

temp(太通用)、My Home/temp(含空格)、/a/b/(以斜杠开头结尾)、十层以上的超深嵌套。

  • 加一个自己的项目前缀,避免和别人的主题撞车(公共服务上尤其重要);
  • 用英文小写和中划线,层级不要太深(3~4 层足够);
  • 主题里不要带空格和特殊字符,不要以 / 开头或结尾;
  • $SYS/ 开头的主题是 Broker 的内部监控主题,普通客户端无法订阅。

两个通配符(只能用于订阅)

通配符含义示例能匹配到
+单层通配,只占一层myhome/+/tempmyhome/livingroom/temp、myhome/bedroom/temp
#多层通配,必须放最后myhome/#myhome 下所有层级的所有主题
注意:通配符只能出现在订阅里,发布消息时必须使用精确主题。# 单独订阅会收到 Broker 上所有消息,在公共服务里请克制使用。

特殊主题

  • $SYS/ 开头的主题是 Broker 的内部监控主题(连接数、消息量等),普通客户端无法订阅;
  • $share/组名/主题 是共享订阅的写法(见第 2 章),同样只用于订阅。
4

服务质量 QoS

QoS(Quality of Service)决定消息投递的可靠程度,发布和订阅时可以分别指定,实际生效的是两者中较低的一级。

级别语义代价适用场景
QoS 0最多一次(发了就算)最低,可能丢传感器周期上报,丢一条无所谓
QoS 1至少一次(可能重复)中等,需确认控制指令、重要状态变更
QoS 2恰好一次(不丢不重)最高,四次握手计费、关键计数等绝不能重复的场景

🤔 怎么选?

90% 的 IoT 场景用 QoS 0 或 1 就够了。高频遥测用 0;怕丢的指令用 1,并在业务上容忍/去重。QoS 2 很少需要。

⚠️ QoS 1 的坑

"至少一次"意味着可能收到重复消息(比如重连后补发)。消费端做幂等处理(比如按消息里的时间戳/序号去重)。

投递过程是怎么确认的?

QoS 握手流程(简化版)
# QoS 1:两步确认
发布者 PUBLISH  ────────▶  Broker
发布者 ◀──────── PUBACK   # 收到确认,发布者才丢弃消息;没收到就重发

# QoS 2:四步握手,保证不重
发布者 PUBLISH  ────────▶  Broker
发布者 ◀──────── PUBREC   # Broker 已收下并记录
发布者 PUBREL   ────────▶  Broker   # 可以投递给订阅者了
发布者 ◀──────── PUBCOMP  # 全流程完成

可以看到级别越高、往返报文越多,延迟和开销也越大——这就是为什么高频遥测推荐 QoS 0 的原因。另外注意:QoS 1/2 的补发依赖持久会话(Clean Session = false),否则断开期间 Broker 不会替你存消息。

5

保留消息与遗嘱消息

📌 保留消息(Retained)

发布时勾选 Retain,Broker 会把这个主题的最后一条消息存下来。之后任何人新订阅该主题,立刻就能收到这条"历史最新值",不用等下一次发布。

典型用法:设备状态(online/offline)、当前配置。想清除保留消息,向该主题发布一条空内容 + Retain 的消息即可。

🪦 遗嘱消息(Last Will)

连接时预先"立遗嘱":指定一个主题和内容。如果客户端异常断开(断网、崩溃,没走正常 DISCONNECT),Broker 就自动把遗嘱消息发布出去。

典型用法:设备上线时发 status=online(Retain),遗嘱设为 status=offline,其他人订阅该主题就能实时感知设备掉线。

黄金组合:Retained + Last Will 是做"设备在线状态监控"的标准姿势,几乎所有物联网项目都会用到,完整时序见第 11 章。

用起来长什么样

状态监测的标准套路
# 1. 连接时就立好遗嘱:异常断开时代发 offline
连接参数: Last Will Topic   = myproj/esp32-001/status
          Last Will Payload = offline,也勾选 Retain

# 2. 连接成功后立刻报在线(保留消息)
PUBLISH myproj/esp32-001/status = online  (Retain)

# 3. 任何人订阅这个主题,立刻知道设备在不在线
SUBSCRIBE myproj/+/status
两个细节:① 正常退出时先发一条空内容 Retain 清掉保留状态、再优雅断开,遗嘱就不会被误触发;② 遗嘱只能"断前立",连上之后不能再改。
6

连接参数与返回码

任何 MQTT 客户端连接时都要填这几样,理解它们能少走很多弯路:

🆔 Client ID(客户端标识)

你在 Broker 上的唯一身份证。同一个 Broker 上 Client ID 不允许重复——如果两台设备用了同一个 ID,会互相把对方挤下线(表现为反复掉线重连)。建议用"项目名 + 设备编号",如 myproj-esp32-001。

👤 用户名 / 密码

本站已关闭匿名连接,必须先在账户中心注册。明文端口(1883/8083)上密码是明文传输,涉及真实业务请优先用 TLS 端口。

💓 Keep Alive(心跳间隔)

客户端每隔一段时间发一次心跳保活,默认常见 60 秒。移动网络/NAT 环境下可适当调小(30~45 秒)防止被中间设备切断;值设为 0 表示不心跳。

🧹 Clean Session / Clean Start

为 true 时每次连接都是"全新会话",断开后订阅关系全部丢弃;为 false 时 Broker 会保留订阅和未投递的 QoS 1/2 消息,重连后补发。初学者保持默认(true)即可。

连接被拒?先看返回码

连接失败时客户端会给出 CONNACK 返回码(MQTT 5.0 的 reason code 更细,下面是常见对照):

rc(3.1.1)含义在本站通常意味着
0连接成功一切正常,可以开工
1协议版本被拒客户端协议版本选错(如选了 3.1),改成 3.1.1 或 5.0
2Client ID 被拒ID 为空或含非法字符;本站不允许匿名,ID 不能留空
3服务不可用认证服务暂时不可达,稍后重试;持续出现请联系维护者
4用户名/密码格式错误凭据字段缺失或非法,检查是否复制完整
5未授权最常见:账户不存在或密码错误,去账户中心核对
记忆口诀:4 是"凭据格式不对",5 是"人不对"(账户或密码错)。90% 的连接失败都是 rc=5。
7

本站接入指南

三步:到账户中心注册 → 选一个端口连接 → 订阅发布。四个端口地址如下,点右侧按钮复制:

TCPmqtt://mqttyyc.top:1883
TLS ★mqtts://mqttyyc.top:8883
WSws://mqttyyc.top:8083/mqtt
WSSwss://mqttyyc.top:8084/mqtt

从零到第一条消息

  1. 打开账户中心注册:只需用户名和密码,不需要邮箱和手机号;用户名一旦注册不可更改,想好再填;
  2. 选一个端口按上面的地址连接,Client ID 给每台设备用唯一值;
  3. 先订阅 mytest/#,再向 mytest/hello 发布一条消息,自己能收到就说明收发都通了;
  4. 把主题换成自己项目的前缀,正式开始。

选哪个端口?

你的场景推荐端口原因
生产环境 / 真实业务8883(TLS)凭据和数据全程加密
嵌入式快速测试(内网)1883(TCP)最省资源,所有客户端都支持
浏览器 / 小程序(HTTPS 页面)8084(WSS)HTTPS 页面只能连 wss
浏览器(HTTP 页面 / 本地调试)8083(WS)无加密,仅适合调试

使用限制(普通账户)

  • 单条消息 ≤ 64 KB;单客户端订阅 ≤ 100 个主题;
  • 每端口每秒最多 5 个新连接;单连接发布 ≤ 100 条/秒 且 ≤ 128 KB/秒(超限会被暂停接收,不会断连);
  • 单个 IP 每天最多注册 2 个账户;频繁断连重连会触发防抖检测被临时封禁;
  • 需要更大额度可申请超级用户(消息 ≤ 1 MB、订阅不限、专属端口 1884)。
小公约:主题是公共的,请加上自己的项目前缀;以数据上报为主,避免大量主题广播下发;别频繁断连重连,触发防抖检测会被临时封禁。
8

客户端实战

下面是最常用的五种接入方式,把 your_username / your_password 换成自己的就能用。

8.1 命令行(mosquitto-clients,最快验证)

服务器上调试或快速验证连通性,两条命令就够:

bash · apt install mosquitto-clients / brew install mosquitto
# ── 第一条命令:订阅(持续接收消息,按 Ctrl+C 停止)──
# -h 服务器地址  -p 端口  -u 用户名  -P 密码
# -i Client ID(自己随便起名,但要全网唯一,别和别人撞)
# -t 要订阅的主题(# 是通配符,收下 mytest 下所有消息)
# -v 打印时把主题也一起显示出来
mosquitto_sub -h mqttyyc.top -p 1883 \
  -u your_username -P your_password \
  -i myproj-cli-sub -t 'mytest/#' -v

# ── 第二条命令:发布一条消息(另开一个终端窗口执行)──
# -m 后面是消息内容;发完命令自动退出
mosquitto_pub -h mqttyyc.top -p 1883 \
  -u your_username -P your_password \
  -i myproj-cli-pub -t mytest/hello -m '你好 MQTT'

# 想要加密连接:把端口换成 8883,再加上系统 CA 证书路径:
#   -p 8883 --capath /etc/ssl/certs

8.2 MQTTX 图形客户端(推荐新手)

下载 MQTTX(桌面版或网页版),新建连接:

MQTTX 连接配置
Name:     mqttyyc
Host:     mqttyyc.top
Port:     8883   (勾选 SSL/TLS)
Username: your_username
Password: your_password

连上后左下角添加订阅(如 mytest/#),右上角发布一条消息试收试发。

8.3 Python(paho-mqtt)

main.py · pip install paho-mqtt
import paho.mqtt.client as mqtt  # 导入 MQTT 客户端库(先 pip install paho-mqtt 安装)

# 连接成功时自动调用的回调函数;rc 是返回码,0 表示成功(对照第 6 章)
def on_connect(client, userdata, flags, rc):
    print('已连接, rc =', rc)
    client.subscribe('mytest/#')  # 连上后立刻订阅:# 收下 mytest 下所有主题

# 每收到一条消息就自动调用的回调函数
def on_message(client, userdata, msg):
    # msg.topic 是主题;payload 是字节串,decode() 把它转成文字
    print(msg.topic, '->', msg.payload.decode())

client = mqtt.Client(client_id='myproj-py-001')  # 创建客户端,ID 每台设备唯一
client.username_pw_set('your_username', 'your_password')  # 换成账户中心注册的账号密码
client.tls_set()  # 开启 TLS 加密,配合下面的 8883 端口
client.on_connect = on_connect  # 登记两个回调函数
client.on_message = on_message
client.connect('mqttyyc.top', 8883)  # 连接 Broker(8883 是加密端口)
client.publish('mytest/hello', '你好 MQTT')  # 发布第一条消息
client.loop_forever()  # 常驻运行:维持连接并持续接收消息

8.4 JavaScript(MQTT.js,浏览器用 WSS)

app.js · npm install mqtt
import mqtt from 'mqtt';  // 引入 MQTT.js 库(先 npm install mqtt 安装)

// 浏览器 / HTTPS 页面必须用 wss:// 加密地址 8084,结尾 /mqtt 不能丢
const client = mqtt.connect('wss://mqttyyc.top:8084/mqtt', {
  username: 'your_username',  // 换成账户中心注册的账号
  password: 'your_password',  // 换成对应密码
});

client.on('connect', () => {      // 连接成功的回调
  client.subscribe('mytest/#');   // 订阅主题:# 收下 mytest 下所有消息
  client.publish('mytest/hello', '你好 MQTT');  // 发布一条试试
});
client.on('message', (topic, payload) => {  // 收到消息的回调
  console.log(topic, '->', payload.toString());  // 打印主题和内容
});

8.5 ESP32 / Arduino(PubSubClient)

main.ino · Arduino IDE 安装 PubSubClient
#include <WiFi.h>          // ESP32 官方 WiFi 库
#include <PubSubClient.h>  // MQTT 客户端库(Arduino IDE 库管理器搜 PubSubClient 安装)

WiFiClient wifiClient;     // 底层 TCP 连接对象
PubSubClient mqtt(wifiClient);  // MQTT 客户端,建立在上面的 TCP 之上

void setup() {  // 开机只执行一次
  WiFi.begin("你的WiFi", "WiFi密码");  // 连接家里 WiFi,换成自己的
  while (WiFi.status() != WL_CONNECTED) delay(300);  // 没连上就等,直到连上为止

  mqtt.setServer("mqttyyc.top", 1883);  // 指定 Broker 地址和端口
  while (!mqtt.connected()) {  // 连不上就一直重试
    // 三个参数:Client ID(每台设备必须唯一)、用户名、密码
    mqtt.connect("myproj-esp32-001", "your_username", "your_password");
    delay(500);
  }
  mqtt.subscribe("mytest/#");  // 订阅主题,准备收消息
  mqtt.publish("mytest/hello", "你好 MQTT");  // 发布第一条消息
}

void loop() {  // 之后反复循环执行
  mqtt.loop();  // 必须经常调用:收消息、发心跳全靠它
}
ESP32 提示:裸 PubSubClient 走 TLS 较麻烦(需 WiFiClientSecure + 根证书),开发阶段用 1883 即可;真要加密建议换支持 TLS 的库(如 AsyncMqttClient + SSL)。
想要完整版?本章示例是帮你理解的最小版本;带重连退避、遗嘱、保留状态、指令回执的"改凭据就能跑"完整模板,直接去第 13 章 复制即用模板库抄。
9

消息体(Payload)设计

MQTT 不关心消息内容,它只搬运一串字节,格式由收发双方自己约定。绝大多数项目选 JSON,可读、好调试、各语言都有现成库。

推荐的 JSON 格式

一条典型的传感器上报
{
  "dev": "esp32-001",    // 设备标识,方便定位
  "type": "env",        // 数据类型,方便消费端分发
  "temp": 26.5,          // 数值直接用数字,别转字符串
  "humi": 61,
  "batt": 3.92,
  "ts": 1755600000      // 时间戳,去重和排序都靠它
}
  • 带上设备标识和时间戳,排查问题和 QoS 1 去重都用得上;
  • 字段名短一点(temp 而非 temperature),高频上报能省不少流量;
  • 一条消息只装一类数据,别把所有传感器塞进一个巨型 JSON,按主题拆开发更好订阅。

体积与二进制

  • 普通账户单条 ≤ 64 KB,实际应尽量控制在几 KB 以内;大文件请拆分、压缩或改用 HTTP 传输,不要硬塞 MQTT;
  • 只上报变化的数据(增量上报),比每次全量轮询省流量得多;
  • 追求极致体积可以用二进制协议(如 protobuf、自定义字节序),但要求收发两端都实现解析,小项目不值得。
约定在先:Payload 格式没有服务端校验,发错了格式 Broker 照样转发——所以文档/约定要写在项目里,消费端记得容错解析,别因为一条脏数据崩掉。
10

安全与最佳实践

本站是公共 Broker:任何注册用户都能订阅任何主题、向任何主题发布。请把每一个主题都当作公开广场来设计你的系统。

🔒 尽量走 TLS

明文端口(1883/8083)上用户名密码是明文传输。涉及真实业务请用 8883 / 8084,凭据和数据全程加密。

🗄️ 凭据别写死在代码里

密码放到环境变量、配置文件或固件加密区,不要提交到公开仓库;固件量产时最好每台设备一个账户。

📵 别传敏感内容

任何人知道主题名就能订阅。个人隐私、密钥、口令这类内容不要放进消息体;确需传输请先自行加密。

🛡️ 指令要验证

任何人也能向你的控制主题发布消息。设备端对收到的指令做基本校验(来源约定、字段合法性),别收到啥执行啥。

  • 给主题加项目前缀不仅是防撞车,也是隐私边界:别人不容易猜到你的设备主题;
  • 怀疑账户泄漏(比如密码被提交到公开仓库)时,尽快通过邮件联系维护者重置密码;
  • 客户端开启自动重连时加退避(如 2 秒起步、逐步翻倍),避免重连风暴触发封禁。
忘记密码怎么办?本站注册不需要邮箱,无法自助找回。请发邮件到 softheartedyyc@gmail.com 说明账户名,维护者会协助重置。
11

经典项目模式

三个几乎每个物联网项目都会用到的模式,直接套用。

模式一:传感器周期上报

设备定时把数据发到自己的主题,服务端订阅入库展示。高频数据用 QoS 0,配合第 9 章的 JSON 格式:

上报与消费
# 设备:每 30 秒发布一次
PUBLISH farm/greenhouse-01/temp  {"v": 26.5, "ts": 1755600000}  QoS 0

# 服务端:一个通配订阅收下整个大棚
SUBSCRIBE farm/greenhouse-01/#

模式二:设备在线监测(Retained + 遗嘱)

第 5 章讲了原理,完整时序长这样:

在线状态时序图
设备 Broker 应用 ① CONNECT(遗嘱: offline) ② PUBLISH status=online(Retain) ③ 推送 online(新订阅也立刻收到) 异常断网(没正常退出) ④ Broker 代发遗嘱 offline 立刻知道掉线 ⑤ 网络恢复,重连后再发 online(Retain) ⑥ 推送 online 恢复在线 ✅

模式三:远程控制与回执

下发指令要"有去有回":设备收到指令后回一条 ack,应用端才知道执行结果:

指令与回执的主题约定
# 应用下发(QoS 1,怕丢)
PUBLISH myproj/esp32-001/cmd/set  {"led": true, "id": 42}

# 设备订阅自己的指令主题
SUBSCRIBE myproj/esp32-001/cmd/#

# 设备执行后回执(带上指令 id 方便对应)
PUBLISH myproj/esp32-001/cmd/set/ack  {"id": 42, "ok": true}

# 应用订阅回执
SUBSCRIBE myproj/esp32-001/cmd/+/ack
小结:上报用 QoS 0 轻快为主,指令用 QoS 1 + ack 保证到位,状态用 Retained + 遗嘱——三招组合起来就是一个完整的设备接入骨架。
12

工业机器人与 PLC 接入

车间数采、远程监工类项目越来越多地要把 PLC 和工业机器人的数据搬到 MQTT 上。这一章讲清楚通用的接入姿势、各品牌的常见连法,以及必须守住的安全边界。

12.1 通用架构:设备不直接连 Broker

工业设备讲究“稳定优先”,一般不会让 PLC / 机器人控制器直接连公共 Broker,标准姿势是中间加一层网关:

车间数采的典型链路
PLC / 机器人控制器
      │  现场总线或厂商接口(Modbus、PROFINET、MC、FOCAS…)
      ▼
IoT 网关 / 边缘上位机        ← 协议转换、缓存补发、主题映射都在这层
      │  MQTT(推荐 TLS 8883)
      ▼
Broker(mqttyyc.top)
      │
      ▼
上位监控 / MES / 看板、报警推送

这样做的好处:设备端程序不用改;网关断线时能缓存补发;多台设备共用一个网关连接,Client ID 和账户都好管理。

12.2 PLC 怎么接

PLC 类型常见连法
西门子 S7-1200/1500较新固件可用官方 TLS + MQTT 通信库直接连;也可用第三方 MQTT 库块,在 PLC 里直接发布订阅
三菱 FX/Q/L 等多数没有原生 MQTT,常用网关按 MC 协议 / Modbus 采集后转 MQTT,或在工控机上跑桥接程序
欧姆龙 / 台达 / 信捷等同上,Modbus / 专用协议网关转 MQTT 是最省事的方案
DIY / 学习场景树莓派、工控机跑桥接程序:pymodbus 轮询寄存器 → paho-mqtt 发布,见第 8 章的 Python 示例

12.3 工业机器人怎么接

主流工业机器人控制器都不原生支持 MQTT,实际项目里基本是两条路:

🖥️ 边缘机采集转发

用厂商接口取数据:ABB 的 PC SDK、FANUC 的 FOCAS、KUKA 的 RSI / 以太网接口、安川的高速服务器等。边缘机读到坐标、节拍、报警后按第 9 章的 JSON 格式发布。

🔌 机器人状态进 PLC

机器人把运行 / 报警 / 节拍等状态通过硬接线 IO 或现场总线送进 PLC,由 PLC 统一转 MQTT。链路最短、最好维护,产线改造里最常用。

12.4 主题与信号设计

套用第 11 章的“上行 + 下行 + 回执”模式,按产线 / 设备分层:

车间数采的主题参考
factory/line1/plc-01/data       # PLC 数据上行:QoS 0,周期发布
factory/line1/plc-01/cmd        # MES 下发指令:QoS 1,怕丢
factory/line1/plc-01/cmd/ack    # PLC 执行回执:带上指令 id
factory/line1/robot-01/status   # 机器人状态:Retained + 遗嘱(见第 5 章)
factory/line1/robot-01/alarm    # 报警事件:QoS 1,触发即发
联调建议:先在办公室电脑上用 MQTTX 模拟网关发布,把主题命名、JSON 字段和回执流程都验证好,再接真设备——能省大量现场时间。

12.5 必须守住的边界

安全红线:急停、安全门、光栅等安全信号必须留在硬接线 IO / 安全总线里,毫秒级实时控制回路继续走 PROFINET / EtherCAT / CC-Link 等现场总线——任何情况下都不要交给 MQTT。
  • 本站这类公共 Broker 适合学习、联调和小规模试点;正式生产建议自建私有 Broker,做好主题鉴权、TLS 和网络隔离;
  • 工艺参数、产量等数据出车间前要做脱敏或加密评估,别让产线数据“裸奔”;
  • 网关到 Broker 的链路要有断线缓存和退避重连,车间网络抖动很常见,别把 Broker 打出重连风暴。
13

复制即用模板库

第 8 章的示例是帮你理解的最小版本,这一章是"改个凭据就能跑"的完整文件:重连退避、遗嘱、保留状态、指令回执全部内置。点右上角按钮复制全文,保存成对应文件名直接运行。

13.1 Python 设备端完整模板

device_template.py · pip install paho-mqtt
# device_template.py —— 改凭据即可运行(基于 paho-mqtt 2.x,1.x 见注释)
import json, os, time                # Python 自带库:JSON 编解码 / 环境变量 / 时间
import paho.mqtt.client as mqtt     # MQTT 客户端库,先 pip install paho-mqtt

# ========= 改成你自己的 =========
USERNAME  = os.getenv("MQTT_USER", "your_username")  # 优先读环境变量,否则用后面的默认值
PASSWORD  = os.getenv("MQTT_PASS", "your_password")  # 把默认值换成自己的账号密码
CLIENT_ID = "myproj-py-001"        # 在 Broker 上的身份证,每台设备必须唯一
PREFIX    = "myproj/py-001"        # 自己的主题前缀,避免和别人撞车
# ======================================

# 连接成功(或失败)时自动调用的回调
def on_connect(client, userdata, flags, reason_code, properties):
    # paho 1.x 用户:签名改为 (client, userdata, flags, rc),创建行见下方注释
    if reason_code == 0:                # 0 表示连接成功(对照第 6 章返回码表)
        print("已连接")
        client.subscribe(PREFIX + "/cmd/#", qos=1)  # 订阅自己的指令主题,QoS 1 怕丢
        client.publish(PREFIX + "/status", "online", qos=1, retain=True)  # 报在线(保留消息,见第 5 章)
    else:
        print("连接失败:", reason_code)  # 失败时打印原因码好排查

# 每收到一条订阅的消息就自动调用
def on_message(client, userdata, msg):
    print("收到:", msg.topic, msg.payload.decode())  # payload 是字节,decode() 转文字
    if msg.topic.endswith("/cmd/set"):          # 收到的是“设置”指令 → 回执告诉对方已执行
        client.publish(PREFIX + "/cmd/set/ack",
                       json.dumps({"ok": True, "ts": int(time.time())}), qos=1)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)  # 创建客户端(2.x 写法)
# paho 1.x 用户:上一行改为 mqtt.Client(client_id=CLIENT_ID)
client.username_pw_set(USERNAME, PASSWORD)   # 设置登录账号密码
client.tls_set()                                # 开启加密;删掉这行则用明文 1883
client.will_set(PREFIX + "/status", "offline", qos=1, retain=True)  # 遗嘱:异常掉线时 Broker 代发 offline
client.on_connect = on_connect              # 登记回调函数
client.on_message = on_message
client.reconnect_delay_set(2, 30)               # 断线后 2 秒起重连,逐渐延长到 30 秒,防重连风暴
client.connect_async("mqttyyc.top", 8883, keepalive=45)  # 异步连接,每 45 秒发一次心跳
client.loop_start()                            # 后台线程维持连接、收发消息

try:
    n = 0
    while True:                             # 主循环:模拟传感器定时上报
        n += 1
        client.publish(PREFIX + "/data", json.dumps({
            "dev": CLIENT_ID, "temp": 25 + n % 5, "ts": int(time.time())}), qos=0)  # 上报用 QoS 0 轻快即可
        time.sleep(5)                           # 每 5 秒上报一次
except KeyboardInterrupt:                  # 按 Ctrl+C 退出时优雅收尾
    client.publish(PREFIX + "/status", "offline", qos=1, retain=True)  # 主动报离线,避免误触发遗嘱
    client.loop_stop()

13.2 Python 服务端采集存 CSV

collector_template.py · 订阅自己前缀下所有消息存 CSV
# collector_template.py —— 服务端示例:把收到的消息存进 CSV 表格,Ctrl+C 退出
import csv, os, time                # Python 自带库:写 CSV / 环境变量 / 时间
import paho.mqtt.client as mqtt     # MQTT 客户端库

USERNAME  = os.getenv("MQTT_USER", "your_username")  # 换成自己的账号
PASSWORD  = os.getenv("MQTT_PASS", "your_password")  # 换成自己的密码
SUB_TOPIC = "myproj/#"                 # 订阅的主题:# 收下 myproj 前缀下所有消息,换成自己的前缀
CSV_FILE  = "messages.csv"           # 保存的文件名,用 Excel 就能打开

# 连接成功后自动订阅
def on_connect(client, userdata, flags, reason_code, properties):
    print("已连接,订阅:", SUB_TOPIC)
    client.subscribe(SUB_TOPIC, qos=1)  # QoS 1:断线重连后能补收期间消息

# 每收到一条消息就追加写入 CSV 一行
def on_message(client, userdata, msg):
    with open(CSV_FILE, "a", newline="", encoding="utf-8") as f:  # "a" = 追加模式,不覆盖旧数据
        csv.writer(f).writerow([time.strftime("%F %T"), msg.topic,  # 三列:时间、主题、内容
                                msg.payload.decode(errors="replace")])  # errors 防止脏字节导致报错
    print("已存:", msg.topic)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="myproj-collector-001")  # 创建客户端,ID 唯一
client.username_pw_set(USERNAME, PASSWORD)  # 登录凭据
client.tls_set()                          # 开启加密,配合 8883
client.on_connect = on_connect           # 登记回调
client.on_message = on_message
client.reconnect_delay_set(2, 30)          # 重连退避
client.connect("mqttyyc.top", 8883, keepalive=45)  # 连接 Broker
client.loop_forever()                    # 常驻运行,一直收消息存文件

13.3 ESP32 完整模板(重连 + 遗嘱 + 控灯)

esp32_template.ino · Arduino IDE 装 PubSubClient
// esp32_template.ino —— 改 WiFi 和凭据后直接烧录
#include <WiFi.h>          // WiFi 库
#include <PubSubClient.h>  // MQTT 库(Arduino IDE 库管理器安装)

// ========= 改成你自己的 =========
const char* WIFI_SSID = "你的WiFi";          // WiFi 名称
const char* WIFI_PASS = "WiFi密码";          // WiFi 密码
const char* MQTT_USER = "your_username";   // 账户中心注册的账号
const char* MQTT_PASS = "your_password";   // 对应密码
const char* CLIENT_ID = "myproj-esp32-001";   // 每台设备必须唯一
const char* PREFIX    = "myproj/esp32-001";  // 自己的主题前缀
// ================================

WiFiClient wifiClient;          // 底层 TCP 连接
PubSubClient mqtt(wifiClient);  // MQTT 客户端
unsigned long lastReport = 0;   // 记录上次上报时间,用于定时

// 连接家里 WiFi,没连上就一直等
void connectWifi() {
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nWiFi 已连接");
}

// 收到订阅消息时的回调:topic 是主题,payload 是内容字节
void onMessage(char* topic, byte* payload, unsigned int len) {
  String t = String(topic), body = String((char*)payload).substring(0, len);  // 把字节转成字符串好处理
  if (t.endsWith("/cmd/set")) {                 // 收到开关灯指令:内容里有 true 就点亮
    bool on = body.indexOf("true") >= 0;
    digitalWrite(LED_BUILTIN, on ? HIGH : LOW);  // 控制开发板自带 LED
    mqtt.publish((String(PREFIX) + "/cmd/set/ack").c_str(),  // 回执:告诉应用端已执行
                 ("{\"ok\":true,\"led\":" + String(on ? "true" : "false") + "}").c_str());
  }
}

// 开机初始化,只跑一次
void setup() {
  Serial.begin(115200);            // 打开串口监视器方便看调试输出
  pinMode(LED_BUILTIN, OUTPUT);  // LED 引脚设为输出
  connectWifi();
  configTime(8 * 3600, 0, "ntp.aliyun.com");    // 联网校时(东八区),这样上报能带真实时间戳
  mqtt.setServer("mqttyyc.top", 1883);            // Broker 地址;要加密换 WiFiClientSecure + 8883
  mqtt.setCallback(onMessage);   // 登记收消息回调
  mqtt.setKeepAlive(45);           // 每 45 秒心跳一次
}

// 主循环,反复执行
void loop() {
  if (WiFi.status() != WL_CONNECTED) connectWifi();  // WiFi 断了就重连
  if (!mqtt.connected()) {          // MQTT 断了就重连
    // connect 参数较多:Client ID、账号、密码、遗嘱主题、遗嘱 QoS、遗嘱保留、遗嘱内容 offline
    // 遗嘱作用:设备异常掉线时 Broker 自动代发 offline(见第 5 章)
    if (mqtt.connect(CLIENT_ID, MQTT_USER, MQTT_PASS,
                     (String(PREFIX) + "/status").c_str(), 1, true, "offline")) {
      mqtt.publish((String(PREFIX) + "/status").c_str(), "online", true);  // 连上后报在线(保留消息)
      mqtt.subscribe((String(PREFIX) + "/cmd/#").c_str());  // 订阅自己的指令主题
      Serial.println("MQTT 已连接");
    }
  }
  mqtt.loop();  // 必须经常调用:收消息、发心跳全靠它
  if (millis() - lastReport > 5000) {             // 距上次上报超过 5 秒 → 上报一次
    lastReport = millis();
    // 手工拼 JSON:dev 设备名、rssi 信号强度、up 运行秒数、ts 时间戳
    String json = "{\"dev\":\"" + String(CLIENT_ID) + "\",\"rssi\":" + String(WiFi.RSSI()) +
                  ",\"up\":" + String(millis() / 1000) + ",\"ts\":" + String((long)time(nullptr)) + "}";
    mqtt.publish((String(PREFIX) + "/data").c_str(), json.c_str());
  }
}

13.4 Web 调试页(存成 html 双击就用)

不想装任何工具?把下面整份存成 mqtt-debug.html,双击用浏览器打开,填上账户密码就能在网页里订阅发布(走 WSS 8084):

mqtt-debug.html · 单文件调试页
<!-- 保存为 mqtt-debug.html,双击打开即用 -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>MQTT Web 调试</title>
<script src="https://unpkg.com/mqtt@5/dist/mqtt.min.js"></script>
<style>
  body { font: 14px/1.7 system-ui, sans-serif; max-width: 760px; margin: 24px auto; padding: 0 16px; }
  input, button { padding: 6px 10px; margin: 3px 2px; border: 1px solid #888; border-radius: 6px; }
  #log { height: 280px; overflow: auto; background: #12261b; color: #9fd8ae;
         padding: 10px; border-radius: 8px; white-space: pre-wrap; }
</style>
</head>
<body>
<h2>MQTT Web 调试页</h2>
<input id="user" placeholder="用户名">
<input id="pass" type="password" placeholder="密码">
<button onclick="doConnect()">连接</button>
<button onclick="client && client.end()">断开</button><br>
<input id="sub" value="mytest/#" size="18">
<button onclick="client.subscribe(sub.value)">订阅</button>
<input id="topic" value="mytest/hello" size="14">
<input id="msg" value="你好 MQTT" size="14">
<button onclick="client.publish(topic.value, msg.value)">发布</button>
<pre id="log"></pre>
<script>
let client;                              // MQTT 客户端对象,连接前是空的
const logEl = document.getElementById("log");       // 页面底部的日志区域
const log = (s) => { logEl.textContent += s + "\n"; };  // 往日志里追加一行

// 点“连接”按钮时执行
function doConnect() {
  client = mqtt.connect("wss://mqttyyc.top:8084/mqtt", {  // 浏览器必须用 wss 加密地址
    username: user.value,   // 从输入框读取账号密码
    password: pass.value,
    clientId: "web-debug-" + Math.random().toString(16).slice(2, 8),  // 随机 ID,避免和别人撞
    reconnectPeriod: 3000,  // 断线 3 秒后自动重连
  });
  client.on("connect", () => log("✔ 已连接"));           // 连接成功→日志提示
  client.on("message", (t, p) => log(t + " -> " + p.toString()));  // 收到消息→打印主题和内容
  client.on("error", (e) => log("✘ " + e));            // 出错→打印错误信息
}
</script>
</body>
</html>
跑模板前:先到账户中心注册账户,把模板里的 your_username / your_password / myproj 前缀全部换成自己的;Client ID 保证每台设备唯一。
14

常见问题排查

先对照第 6 章的返回码表,再查下面的现象清单:

现象大概率原因怎么查
连接立刻被拒(rc=4/5)用户名密码错误,或没注册到账户中心确认账户存在;重新复制凭据,注意前后空格
连上几秒就被踢另一台设备用了相同 Client ID给每台设备换唯一的 Client ID
连 8883 报证书错误客户端没开 TLS,或不信任公共 CA确认协议选的是 mqtts/SSL;关闭自签严格校验或改用系统根证书
发布了但订阅方收不到主题不匹配 / 通配符写错 / QoS 搭配检查订阅主题是否精确匹配或通配正确;先订阅再发布
网页里连不上HTTPS 页面用了 ws://,或漏了 /mqtt 路径HTTPS 页面改用 wss://mqttyyc.top:8084/mqtt
反复掉线重连频繁断连触发防抖检测(临时封禁 15 分钟),或 Client ID 冲突修复重连风暴(加退避),等 15 分钟自动解除;检查网络稳定性
大消息发不出去超过普通账户 64 KB 限制拆分消息或申请超级用户(≤ 1 MB)
发布被"吞"了(无报错也不转发)触发发布限流(100 条/秒 或 128 KB/秒)限流时 Broker 暂停接收而不断连;降低上报频率或合并消息
订阅报错 / 被拒超过单客户端 100 个主题的订阅上限用通配符合并订阅,或拆分到多个连接
还是解决不了?发邮件到 softheartedyyc@gmail.com,写上你的账户名、客户端类型和报错信息,维护者会直接回复。
15

术语表

术语含义
Broker消息代理服务器,负责接收、过滤、分发消息(本站由 EMQX 提供)
Publisher / Subscriber发布者 / 订阅者,即发送和接收消息的客户端
Topic主题,消息的"频道",用 / 分层
Payload消息体,具体内容,可以是文本也可以是二进制
Client ID客户端在 Broker 上的唯一标识
QoS服务质量等级:0 最多一次 / 1 至少一次 / 2 恰好一次
Retained Message保留消息,新订阅者立刻能收到的最新一条
Last Will遗嘱消息,客户端异常断开时由 Broker 代发
Keep Alive心跳间隔,客户端定期发 PINGREQ 保活,Broker 回 PINGRESP
CONNACK连接应答报文,携带连接成功/失败的返回码(见第 6 章)
PUBACK / PUBREC / PUBREL / PUBCOMPQoS 1/2 投递确认报文,见第 4 章握手流程
Session会话,保存订阅关系和未投递消息的状态
Shared Subscription共享订阅,$share/组名/主题,组内成员负载均衡消费消息
$SYSBroker 内部监控主题前缀,普通客户端不可订阅
TLS/SSL传输加密层,对应本站 8883 / 8084 端口
到这儿就读完啦:回主站看看端口和限制,或直接去账户中心注册,三分钟连上你的第一台设备。
已复制到剪贴板