4. MQTT协议基础与EMQX接入验证

EMQX 是面向物联网场景的高并发 MQTT 消息服务器,部署完成后,第一件必须确认的事不是“端口是否监听”,而是“客户端能否按 MQTT 语义正确接入、发布、订阅并收到消息”。本节围绕 MQTT 的发布/订阅模型、QoS 等级、保留消息与遗嘱消息四个核心机制展开,并使用 mosquitto 客户端对 EMQX 接入进行验证。

一、发布/订阅模型:主题是唯一的路由依据

MQTT 采用发布/订阅(Pub/Sub)模型,与 HTTP 的请求/响应模型有本质区别。发布者(Publisher)不直接把消息发给某个订阅者,而是把消息发布到某个主题(Topic);订阅者(Subscriber)向 Broker 声明自己关心哪些主题;Broker(此处即 EMQX)负责把消息按主题匹配规则转发给所有匹配的订阅者。发布者与订阅者彼此不知道对方的存在,二者通过主题解耦。

主题是一个用斜杠 / 分隔的层级字符串,例如 factory/line1/temp。主题区分大小写,且不需要预先创建。订阅时可以使用两种通配符:+ 匹配单层,# 匹配多层且只能出现在末尾。例如 factory/+/temp 可匹配 factory/line1/temp 与 factory/line2/temp,而 factory/# 可匹配 factory 下所有层级。需要注意,以 $ 开头的主题(如 $SYS/)不会被 # 匹配,EMQX 的系统主题就属于这一类。

下面这张图把发布/订阅模型、QoS 等级、保留消息与遗嘱消息放在同一条链路上,便于对照理解各机制发生的位置。

MQTT 发布/订阅模型与四类核心机制 发布者 Publisher mosquitto_pub topic: factory/line1/temp EMQX Broker 1883 / 8883 / 8083 主题匹配 + 消息路由 会话状态 / 保留消息存储 $SYS/# 系统主题 订阅者 Subscriber mosquitto_sub topic: factory/+/temp PUBLISH 转发匹配消息 QoS 等级(发布者与订阅者各自声明,实际生效取两者较小值) QoS 0 至多一次 发出即忘,可能丢失 QoS 1 至少一次 PUBACK 确认,可能重复 QoS 2 恰好一次 四次握手,开销最大 保留消息 Retained 发布时置 retain=true,Broker 保存该主题最后一条消息 新订阅者一订阅立即收到,无需等待下次发布 发送空载荷可清除该主题的保留消息 遗嘱消息 Will CONNECT 时声明 will topic / payload / qos / retain 客户端异常断开(非正常 DISCONNECT)时由 EMQX 代发 常用于设备离线告警:factory/line1/status = offline

二、QoS 等级:可靠性、开销与重复的权衡

QoS 决定消息投递的可靠性等级,共三档。QoS 0 为“至多一次”,发布者发出后不等待确认,网络抖动或 Broker 重启都可能丢消息,适合高频、可容忍丢失的传感器数据。QoS 1 为“至少一次”,Broker 收到后回 PUBACK,若确认丢失会重发,因此可能重复,适合大多数遥测与指令场景。QoS 2 为“恰好一次”,通过 PUBLISH/PUBREC/PUBREL/PUBCOMP 四次握手保证不丢不重,但开销最大、时延最高,一般用于计费、开关动作等不允许重复的场合。

一个容易踩坑的点是:QoS 是发布与订阅两侧各自声明的,最终生效等级取两者中的较小值。若发布用 QoS 2、订阅用 QoS 0,实际投递仍是 QoS 0。因此排查“为什么消息不可靠”时,必须同时检查两侧参数。

三、保留消息与遗嘱消息

保留消息解决“订阅者上线太晚、错过上一次状态”的问题。发布时加上 retain 标志,EMQX 会为该主题保存最后一条保留消息;之后任何新订阅者一旦订阅匹配主题,会立刻收到这条消息。例如设备每分钟上报一次温度,若使用保留消息,监控端刚启动就能立即拿到当前温度,而不是干等一分钟。清除保留消息的方法是向同一主题发布一条空载荷的保留消息。

遗嘱消息解决“设备掉线如何被感知”的问题。客户端在 CONNECT 报文中预先声明遗嘱主题、载荷、QoS 与 retain 标志;当该客户端异常断开(网络中断、进程被杀、心跳超时)时,EMQX 会代为发布这条遗嘱消息。若客户端主动发送 DISCONNECT 正常断开,遗嘱不会触发。典型用法是设备上线时发布 online 保留消息,同时把遗嘱设为 offline,这样监控端既能知道设备在线,也能在设备掉线时立刻收到通知。

四、用 mosquitto 客户端验证 EMQX 接入

验证前先确认 EMQX 已启动且 1883 端口可访问。以下命令中的 IP、用户名密码均为示例,请替换为实际环境值。

# 1. 查看 EMQX 运行状态(示例)
sudo systemctl status emqx

# 2. 订阅端:订阅通配主题,-v 显示主题名,-q 指定订阅 QoS
mosquitto_sub -h 192.168.1.10 -p 1883 -t 'factory/+/temp' -q 1 -v

# 3. 发布端:发布一条 QoS 1 消息
mosquitto_pub -h 192.168.1.10 -p 1883 -t 'factory/line1/temp' -q 1 -m '26.5'

# 4. 保留消息验证:发布带 -r 的保留消息
mosquitto_pub -h 192.168.1.10 -p 1883 -t 'factory/line1/status' -q 1 -r -m 'online'

# 5. 新开一个订阅端,应立刻收到上一条保留消息
mosquitto_sub -h 192.168.1.10 -p 1883 -t 'factory/line1/status' -q 1 -v

# 6. 清除保留消息:发布空载荷
mosquitto_pub -h 192.168.1.10 -p 1883 -t 'factory/line1/status' -q 1 -r -n

# 7. 遗嘱消息验证:订阅告警主题后,用带遗嘱的客户端连接并强制中断
mosquitto_sub -h 192.168.1.10 -p 1883 -t 'factory/line1/status' -q 1 -v
mosquitto_pub -h 192.168.1.10 -p 1883 -t 'factory/line1/status' -q 1 \
  --will-topic 'factory/line1/status' --will-payload 'offline' --will-qos 1 --will-retain \
  -m 'online' -r
# 在另一终端用 Ctrl+C 强制结束上面的 pub 进程,观察订阅端是否收到 offline

若需要用户名密码接入,可在命令中追加 -u 用户名 -P 密码;若启用了 TLS,则把端口改为 8883 并追加 --cafile ca.crt。EMQX 默认允许匿名接入,生产环境应在 Dashboard 的“访问控制—认证”中关闭匿名并配置认证方式。

五、结果判读与常见问题

验证是否成功,可从三个层面判断。第一,连接层:mosquitto_sub 启动后无报错、不退出,说明 TCP 与 MQTT CONNECT 成功;若提示 Connection Refused: not authorised,多为认证或 ACL 拒绝。第二,路由层:发布后订阅端立即打印出 factory/line1/temp 26.5,说明主题匹配与转发正常;若收不到,优先检查主题拼写、通配符层级以及是否误用了 $SYS 类主题。第三,语义层:保留消息应在订阅瞬间到达,遗嘱消息应在客户端异常断开后到达,若遗嘱未触发,需确认客户端是异常断开而非主动 DISCONNECT。

建议把上述命令整理成一个验证脚本,每次部署新节点后按“订阅—发布—保留—遗嘱”顺序执行一遍,可在几分钟内确认 EMQX 的接入与消息语义均正常。
验证项关键参数预期现象异常时优先排查
基础接入-h / -p订阅端保持连接不退出端口、防火墙、EMQX 进程
发布订阅-t / -q / -v订阅端打印主题与载荷主题拼写、通配符、QoS 取值
保留消息-r新订阅者立即收到最后一条是否带 -r、是否被空载荷清除
遗嘱消息--will-*异常断开后收到 offline是否主动 DISCONNECT、will 参数