4. MQTT客户端实战:mosquitto_pub/sub与Python Paho-MQTT

好,到了真正动手的环节了。前面我们搭好了Broker,配了TLS证书,也搞定了用户名密码。但说句实话,没有客户端连上去收发几条消息,你心里总归是没底的。我自己带团队的时候,经常看到新人把Broker配置得花里胡哨,结果一测才发现端口都连不上——嗯,纸上谈兵要不得。

这一章我们就来干两件事:先用mosquitto自带的命令行工具做一次完整的手动收发,再装好Python的Paho-MQTT库,写几行代码把自动化跑起来。你跟着我走一遍,MQTT客户端这块就算入门了。

本章核心目标:掌握mosquitto_pub/sub的基本用法,完成Paho-MQTT库的安装与基础发布/订阅代码编写。

4.1 用mosquitto_pub/sub做一次手动收发

mosquitto客户端工具是跟着Broker一起安装的。如果你按我上一章的步骤来,现在终端里应该已经能用mosquitto_pub和mosquitto_sub了。不信?先验证一下:

mosquitto_pub --help | head -5
mosquitto_sub --help | head -5

能看到帮助信息,就说明工具就绪了。我个人习惯先开三个终端窗口:一个跑Broker(如果没在后台运行),一个跑订阅者,一个跑发布者。这样能直观看到消息的流动。

4.1.1 最简单的匿名收发(不加密)

先别急着上TLS,我们用一个最简配置验证链路通不通。假设你的Broker跑在本地1883端口:

终端1(订阅者):

mosquitto_sub -h localhost -p 1883 -t "test/topic" -v

-v参数会打印出主题名,方便你确认消息来源。我在项目中遇到过有人忘了加-v,结果发了十条消息都不知道是哪个主题来的——排查起来很痛苦。

终端2(发布者):

mosquitto_pub -h localhost -p 1883 -t "test/topic" -m "Hello MQTT, 我来了!"

按下回车,你会在订阅者终端看到类似这样的输出:

test/topic Hello MQTT, 我来了!

成了。就这么简单。但你要注意,这个通信是明文的,局域网里任何抓包的人都能看到消息内容。所以别在生产环境这么干。

4.1.2 带用户名密码的收发

还记得我们在第三章创建的mqtt_user吗?现在用上它:

订阅者:

mosquitto_sub -h localhost -p 1883 -t "secure/topic" -u mqtt_user -P "YourStrongPassword!" -v

发布者:

mosquitto_pub -h localhost -p 1883 -t "secure/topic" -u mqtt_user -P "YourStrongPassword!" -m "这条消息有身份认证了"

如果你没配密码文件就尝试连接,Broker会直接拒绝——你会看到Connection Refused: not authorised。我曾经在客户现场遇到过这种情况,排查了半天才发现密码文件路径写错了。嗯,路径问题永远是第一坑。

4.1.3 启用TLS加密的收发

这才是我们的重头戏。假设你的CA证书、服务端证书和客户端证书都在/etc/mosquitto/certs/下:

订阅者(TLS + 证书认证):

mosquitto_sub -h your-broker-domain.com -p 8883 \
  --cafile /etc/mosquitto/certs/ca.crt \
  --cert /etc/mosquitto/certs/client.crt \
  --key /etc/mosquitto/certs/client.key \
  -t "tls/topic" -v

发布者(TLS + 证书认证):

mosquitto_pub -h your-broker-domain.com -p 8883 \
  --cafile /etc/mosquitto/certs/ca.crt \
  --cert /etc/mosquitto/certs/client.crt \
  --key /etc/mosquitto/certs/client.key \
  -t "tls/topic" \
  -m "这条消息在TLS隧道里,谁也偷听不到"

注意:如果Broker配置了require_certificate true,客户端必须提供证书;如果只是require_certificate false,客户端可以只验证服务端证书(即只用--cafile)。具体用哪种,取决于你的安全策略。

4.2 理解MQTT客户端的工作流程

在进入Python编程之前,我觉得有必要把客户端的工作流程理清楚。你想想看,一个MQTT客户端从启动到收发消息,到底经历了哪些步骤?

MQTT客户端工作流程 1. 创建客户端实例 2. 设置TLS/认证参数 3. 连接Broker 4. 订阅主题 / 发布消息 5. 进入网络循环(loop) 6. 断开连接(disconnect) 关键说明: • 步骤2和3的顺序可以互换 • 步骤4中订阅和发布可同时进行 • loop是保持连接活跃的关键 • 断开连接前建议先取消订阅

这个流程你记在心里,后面写Python代码的时候,每一步都会对应到具体的API调用。说白了,mosquitto_pub/sub只是帮我们把这几步封装成了命令行参数,而Paho库则给了我们完全的控制权。

4.3 安装Python Paho-MQTT库

Paho-MQTT是Eclipse基金会维护的官方Python客户端库,稳定、文档全、社区活跃。我个人在所有Python MQTT项目里都用它,没出过什么幺蛾子。

4.3.1 安装命令

pip install paho-mqtt

如果你在虚拟环境里(我强烈建议这么做),先激活虚拟环境再安装:

python3 -m venv mqtt_env
source mqtt_env/bin/activate  # Linux/Mac
# 或 mqtt_env\Scripts\activate  # Windows
pip install paho-mqtt

4.3.2 验证安装

打开Python交互环境,试试导入:

python -c "import paho.mqtt.client as mqtt; print(mqtt.__version__)"

如果能输出版本号(比如1.6.1或2.0.0),就说明安装成功了。我记得Paho从2.0版本开始改了一些回调函数的签名,如果你用的是2.x,后面代码里我会标注兼容写法。

版本提示:本课程代码基于paho-mqtt 1.6.x编写,同时兼容2.0+。如果你遇到CallbackAPIVersion相关的报错,说明你装的是2.x,需要在创建客户端时指定callback_api_version=mqtt.CallbackAPIVersion.VERSION1。

4.4 Python Paho-MQTT基础用法

好,工具装好了,我们直接上代码。我会先写一个最简的发布者,再写一个订阅者,然后组合起来跑通。

4.4.1 最简单的发布者(匿名+明文)

import paho.mqtt.client as mqtt
import time

# 1. 创建客户端实例
client = mqtt.Client()

# 2. 连接Broker
client.connect("localhost", 1883, 60)

# 3. 发布消息
client.publish("test/topic", "Hello from Python Paho!")

# 4. 给网络循环一点时间发送消息
time.sleep(1)

# 5. 断开连接
client.disconnect()

这段代码你一看就懂,跟mosquitto_pub的流程一模一样。但有个细节要注意:publish()只是把消息放进了发送队列,真正发出去要靠网络循环。所以我在后面加了个time.sleep(1)——这不是优雅的做法,但演示足够了。

4.4.2 最简单的订阅者(匿名+明文)

import paho.mqtt.client as mqtt

# 回调函数:当收到消息时被调用
def on_message(client, userdata, msg):
    print(f"收到消息:主题={msg.topic}, 载荷={msg.payload.decode()}")

# 创建客户端并绑定回调
client = mqtt.Client()
client.on_message = on_message

# 连接并订阅
client.connect("localhost", 1883, 60)
client.subscribe("test/topic")

# 进入网络循环(阻塞式,持续等待消息)
client.loop_forever()

这里loop_forever()会一直运行,直到你按Ctrl+C中断。它内部会处理心跳、重连、消息分发——你想想看,如果没有这个循环,客户端怎么知道Broker什么时候推送消息过来?

4.4.3 带TLS和用户名密码的完整示例

这才是生产级别的写法。我把发布者和订阅者合并到一个脚本里,方便你测试:

import paho.mqtt.client as mqtt
import ssl
import time

# 配置参数
BROKER = "your-broker-domain.com"
PORT = 8883
TOPIC = "secure/tls/topic"
USERNAME = "mqtt_user"
PASSWORD = "YourStrongPassword!"
CA_CERT = "/etc/mosquitto/certs/ca.crt"
CLIENT_CERT = "/etc/mosquitto/certs/client.crt"
CLIENT_KEY = "/etc/mosquitto/certs/client.key"

# 订阅者的回调
def on_message(client, userdata, msg):
    print(f"[订阅者] 收到: {msg.payload.decode()}")

# 创建客户端
client = mqtt.Client()

# 设置用户名密码
client.username_pw_set(USERNAME, PASSWORD)

# 设置TLS
client.tls_set(
    ca_certs=CA_CERT,
    certfile=CLIENT_CERT,
    keyfile=CLIENT_KEY,
    cert_reqs=ssl.CERT_REQUIRED,
    tls_version=ssl.PROTOCOL_TLS
)

# 绑定回调
client.on_message = on_message

# 连接
client.connect(BROKER, PORT, 60)

# 订阅
client.subscribe(TOPIC)

# 在另一个线程启动网络循环
client.loop_start()

# 发布一条消息
client.publish(TOPIC, "TLS加密的消息,安全到达!")
print("[发布者] 消息已发送")

# 等一会儿让订阅者收到
time.sleep(2)

# 清理
client.loop_stop()
client.disconnect()

证书路径问题:我曾经在Windows上遇到过路径分隔符导致的错误——Python的\需要转义,或者直接用正斜杠/。建议统一用os.path.abspath()或pathlib来处理路径,避免跨平台问题。

4.5 常见问题与避坑指南

写到这里,我把这些年用Paho-MQTT踩过的坑整理一下,你遇到了可以直接翻回来查。

问题现象 可能原因 解决方案
连接超时 Broker地址/端口错误,或防火墙拦截 用telnet或nc先测试端口连通性
Connection Refused Broker未启动,或端口配置不匹配 检查mosquitto是否在运行,ps aux | grep mosquitto
not authorised 用户名密码错误,或ACL未配置 检查密码文件,确认用户有该主题的权限
TLS握手失败 证书不匹配、过期、或CA未正确加载 用openssl s_client单独测试TLS链路
消息收不到 订阅主题与发布主题不一致,或QoS不匹配 检查主题字符串是否完全一致(包括大小写)
loop_start后程序退出 主线程没有等待,网络循环被提前终止 用time.sleep()或input()保持主线程存活

调试小技巧:在connect()之前设置client.on_log = lambda c, u, l, m: print(f"[LOG] {m}"),可以打印出Paho库内部的调试日志,很多诡异问题都能从这里找到线索。

4.6 本章小结

这一章我们干了三件事:用mosquitto_pub/sub跑通了手动收发(从匿名到TLS),理清了MQTT客户端的工作流程,然后用Python Paho-MQTT库写出了自动化版本。你现在应该能自己写一个简单的MQTT客户端了——不管是命令行还是Python脚本。