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客户端从启动到收发消息,到底经历了哪些步骤?
这个流程你记在心里,后面写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脚本。