ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MQTT消息服务器搭建实战:从mosquitto安装到TLS加密与设备接入

MQTT消息服务器搭建实战:从mosquitto安装到TLS加密与设备接入 做物联网、车联网、智能家居这类项目几乎绕不开一个环节设备之间怎么通信。我自己最初的做法是让设备直接连云平台结果调试环境依赖公网测试一个小时有半小时在等网络响应烦得很。后来换了思路在本地 Ubuntu 服务器上搭一个自己的 MQTT broker消息代理所有设备先走本地消息中转调试效率高了一个量级。这个方案的核心工具就是 mosquitto一个轻量级、开源、跨平台的 MQTT 服务器实现。这篇文章我会从协议背景讲起把 mosquitto 的安装、配置文件编排、认证与权限控制、TLS 加密、客户端联调、Docker 部署、常见问题排查全部串起来。适合这几类人参考刚接触 MQTT 想自建消息服务器的物联网开发者准备把设备接入公司内网做联调的嵌入式工程师以及想在个人服务器上搭一套私有消息通道折腾智能家居的玩家。文章里给的命令和配置我会按 Ubuntu 22.04 LTS 实际验证过的方式写Ubuntu 20.04 和 24.04 也基本通用。1. MQTT 到底是什么为什么选 mosquitto1.1 发布订阅模型一次搞懂 Broker、Topic、QoSMQTT 协议最核心的思想是“发布订阅”模型。你可以把它想象成小区快递柜寄件人Publisher把包裹放进柜子Broker收件人Subscriber凭取件码Topic来取寄件人和收件人不需要认识彼此也不需要同时在柜子前出现。整个通信过程由 Broker 统一中转消息不会直接发给某个具体设备而是发到一个主题上谁订阅了这个主题谁就能收到消息。Topic 长这样home/bedroom/temperature由正斜杠分隔层级支持单层通配符和#多层通配符两种匹配方式。比如订阅home//temperature可以收到所有房间的温度值订阅home/#可以收到 home 下所有消息。QoSQuality of Service服务质量表示消息投递的可靠性分为 0、1、2 三档0 是至多一次发出去不管1 是至少一次可能重复2 是恰好一次性能开销最大。实际使用中传感器上报这类数据用 QoS 0 或 1 就够了控制指令建议用 QoS 1QoS 2 绝大多数场景用不上。这套模型的优势在于设备之间完全解耦。以前做点对点通信设备 A 要发数据给设备 B得提前知道 B 的 IP、端口还要约定协议格式一旦设备离线或者网络不稳整个链路就断了。MQTT 把“谁发给谁”变成“谁订阅什么主题”设备只需要关注 Broker 地址和自己的主题其他都不用关心。1.2 mosquitto 的设计哲学和选型对比mosquitto 是 Eclipse 基金会维护的开源项目用 C 语言实现主打“轻量、稳定、省资源”。我见过跑在树莓派上的 mosquitto内存占用也就 20MB 左右在工控机、路由器、NAS 上都能流畅运行。它对 MQTT 3.1.1 和 5.0 协议支持都很完整认证、TLS 加密、ACL 访问控制、WebSocket 监听、遗嘱消息这些能力都有作为单机消息服务器完全够用。平时经常有人问我为什么不直接上 EMQX 或者 HiveMQ我的判断标准是看规模。如果你的场景是几十台设备、几万条消息的小规模自建mosquitto 是性价比最高的选择因为配置简单、依赖少出问题也好排查。EMQX 这类产品功能确实强大支持集群、规则引擎、数据持久化但对应的代价是配置复杂度成倍上升Java 生态带来的内存开销也不小一个人维护有学习成本。反过来如果业务量到了百万级设备、同时在线连接数上万再考虑迁移到专业级 broker 也不迟。小步快跑前期不要让基础设施成为负担。1.3 会说话的“遗嘱”让断线通知有价值MQTT 里有一个容易忽略但非常好用的设计叫 Last Will遗嘱。客户端连接时可以事先声明一个遗嘱主题和消息比如device/status上发offline。如果这个客户端正常断开遗嘱不会触发但如果是非正常断线比如网络闪断、断电、进程崩溃Broker 会立刻替它把遗嘱消息发出去订阅了对应主题的设备马上就知道“这台设备掉线了”。这个机制在设备管理场景里特别实用。我做室内环境监测时给每个传感器节点设置了遗嘱主题网关端订阅了所有节点的遗嘱任何节点非正常离线网关会在几秒内上报到数据库比依赖轮询心跳判断离线要快得多。后面第 5 章配置客户端的时候我会再具体说遗嘱参数怎么填。2. 环境准备与两种安装方式2.1 确认 Ubuntu 环境和网络模式开始动手之前先把系统信息确认清楚。打开终端跑这几条命令lsb_release -a uname -a我这边输出的是 Ubuntu 22.04.4 LTS、x86_64 架构。日常使用中mosquitto 对架构不挑剔x86 的物理服务器、ARM 的开发板如香橙派、树莓派都能跑。如果你用的是虚拟机务必确认网络模式。NAT 模式下虚拟机和宿主机共享一个出口 IP外部设备要访问虚拟机里的 mosquitto需要在 VMware/VirtualBox 里做端口映射桥接模式则让虚拟机直接占用局域网 IP设备访问起来跟访问普通服务器没有区别。就调试而言把虚拟机改成桥接模式能省很多事实在不方便改网络模式就做好宿主机的端口转发。2.2 apt 直接安装 mosquitto 和客户端工具Ubuntu 的官方软件源里就有 mosquitto安装非常省事。我建议把服务和客户端工具一起装上后面调试要用sudo apt update sudo apt install -y mosquitto mosquitto-clients装完以后mosquitto 服务会自动启动并注册为 systemd 服务。顺手确认一下运行状态systemctl status mosquitto ss -lntp | grep 1883看到 1883 端口处于 LISTEN 状态说明默认配置已经生效了。这里有个细节Ubuntu 源里的 mosquitto 2.x 默认只监听本机回环地址 127.0.0.1如果想被局域网内的其他设备访问必须去改配置文件第 3 章会详细说。如果没有主动改配置就指望外部设备能连上这是多数人搭建失败的第一个坑。2.3 安装完先做个冒烟测试服务起没起来光看端口还不够直接发一条消息验证最靠谱。打开两个终端终端 A 订阅mosquitto_sub -h 127.0.0.1 -p 1883 -t test/topic终端 B 发布mosquitto_pub -h 127.0.0.1 -p 1883 -t test/topic -m hello mosquitto终端 A 里能打印出hello mosquitto就说明本地收发链路完全打通了。注意两个 -h 参数如果服务只监听 127.0.0.1这里就必须用 127.0.0.1 访问不能用本机局域网 IP否则会报 Connection refused。3. 配置文件深度解读从匿名到密码认证3.1 配置文件结构和默认行为的秘密mosquitto 的主配置文件在/etc/mosquitto/mosquitto.conf默认内容很少但里面有一行非常关键include_dir /etc/mosquitto/conf.d意思是主配置文件会额外加载/etc/mosquitto/conf.d/目录下所有.conf后缀的文件。日常维护我习惯把自定义配置放在/etc/mosquitto/conf.d/里比如建一个my.conf而不是直接改主配置。这样升级软件包、备份配置文件、回滚变更都方便整个目录一复制就能迁移到新服务器。默认配置下mosquitto 只监听回环地址、允许匿名访问。这对本机调试没问题但对外提供服务就是安全隐患。特别是 2.0 版本之后mosquitto 对配置的敏感度更高很多面向公网的裸奔事故都是因为没设密码、没绑 listen address 导致的。3.2 局域网可访问的最小配置想让局域网设备连上 mosquitto最简单的做法是在 conf.d 下新建一个配置文件sudo vim /etc/mosquitto/conf.d/local.conf写入以下内容listener 1883 0.0.0.0 allow_anonymous truelistener 1883 0.0.0.0表示监听所有网卡的 1883 端口第二个参数如果写成具体的 IP比如192.168.1.10就只有通过该 IP 访问的连接能进来。allow_anonymous true允许匿名连接仅供内网调试。保存后重载服务sudo systemctl restart mosquitto sudo systemctl status mosquitto这时候再用局域网 IP 测试mosquitto_sub -h 192.168.1.10 -t test/topic能收到消息就说明外部访问正常了。另一个验证方式是其他终端设备用 MQTT 客户端软件连这个 IP这个我们留到第 4 章再说。3.3 开启密码认证第一个安全底线匿名访问只能用来临时调试一旦设备正式接入第一件事就是关闭匿名并开启密码认证。步骤很简单第一步创建密码文件并添加第一个用户sudo mosquitto_passwd -c /etc/mosquitto/passwd mqtt_user-c表示新建文件如果文件已存在不加-c就是追加用户。命令执行后会要求输入两次密码密码会以哈希形式写进文件不会明文存储。第二步在配置文件里加载这个密码文件并禁止匿名listener 1883 0.0.0.0 allow_anonymous false password_file /etc/mosquitto/passwd第三步重启服务sudo systemctl restart mosquitto此时再用不带头连接参数的命令比如mosquitto_sub -h 192.168.1.10 -t test/topic会直接报Connection Refused: not authorised。正确用法是加-u和-Pmosquitto_sub -h 192.168.1.10 -p 1883 -u mqtt_user -P 你的密码 -t test/topic发布端同理。这里有个经验账号密码不要用同一个mqtt_user/mqtt_user这种组合在内网可能无所谓但一旦服务暴露到公网扫描器几乎秒破。3.4 ACL 权限控制让设备只能聊自己的事密码认证只解决“谁能进来”的问题没解决“进来之后能干嘛”。默认情况下一个账号可以订阅和发布任意主题。这在设备接入场景里会导致一个问题某个设备误订阅了其他设备的主题可能把所有消息全部收到逻辑一混乱就是几个小时。更严重的是如果有人拿到了弱密码账号可以随意发布控制指令后果很严重。ACLAccess Control List就是为了限制账号可以访问哪些主题。在/etc/mosquitto/conf.d/下新建acl.conf写入acl_file /etc/mosquitto/acl然后在/etc/mosquitto/acl文件里按规则写user mqtt_user topic readwrite sensor/# topic read control/# user admin topic readwrite #这段配置的意思是mqtt_user只能读写sensor/前缀的主题只能读control/前缀的主题admin账号则拥有全部权限。改完后重启服务账号的读写边界就被锁死了。顺便提一句ACL 里还支持匿名用户规则和模式匹配比如topic read home/%u/#能让每个用户只访问自己用户名对应的主题前缀这在多租户场景里很常用。4. 本地联调客户端工具、可视化工具与 Python4.1 命令行联调mosquitto_sub 和 mosquitto_pub 的常用参数命令行工具是调试 MQTT 最轻便的手段参数也不复杂。我这里列几个平时高频使用的组合订阅mosquitto_sub -h 192.168.1.10 -p 1883 -u mqtt_user -P 密码 -t sensor/# -v -d-v会在每条消息前面显示主题-d会打印客户端与 Broker 之间的调试日志查看是否为连接失败、权限拒绝、topic 违规等。排查问题时这两项必开。发布mosquitto_pub -h 192.168.1.10 -p 1883 -u mqtt_user -P 密码 -t sensor/bedroom/temperature -m 26.5 -q 1-q 1指定 QoS 为 1建议在关键数据场景强制使用配合消息去重逻辑来保证可靠性。还有几个不常用但实用的参数-r发送保留消息Broker 会存住最新一条新订阅者立刻能拿到、-l从标准输入逐行读取发送、-f从文件读取消息内容。4.2 可视化调试MQTT Explorer 解决“看不到消息”的焦虑命令行能验证通断但面对十几二十个主题时人眼扫日志效率太低。MQTT Explorer 是我目前用过最顺手的可视化 MQTT 客户端界面像左边是订阅树右边是消息内容能看到每个主题的最新消息和消息到达时间。用它能清楚看到sensor/#下所有子主题的数据不需要一个个手动订阅。MQTT Explorer 跨平台下载解压就能用。连接界面里填上 Broker IP、端口、用户名密码高级设置里还可以配置 TLS。订阅主题填#等于接收全量消息配上一个速度足够快的网络调试体验真心不错。对我个人来说有了 MQTT Explorer排查主题路由问题的速度至少快了三倍很推荐。4.3 Python paho-mqtt把 Broker 接入业务代码实际项目里数据最终要落到数据库或者触发业务逻辑这时候就需要用代码连接 Broker。Python 生态里最常用的是 paho-mqtt安装和用法都非常简单pip install paho-mqtt下面是一个订阅温度主题的完整示例import paho.mqtt.client as mqtt BROKER 192.168.1.10 PORT 1883 TOPIC sensor/# USERNAME mqtt_user PASSWORD your_password def on_connect(client, userdata, flags, rc, propertiesNone): if rc 0: print(连接成功) client.subscribe(TOPIC, qos1) else: print(f连接失败错误码: {rc}) def on_message(client, userdata, msg): print(f{msg.topic} - {msg.payload.decode()}) client mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) client.username_pw_set(USERNAME, PASSWORD) client.on_connect on_connect client.on_message on_message client.connect(BROKER, PORT, keepalive60) client.loop_forever()需要注意mqtt.CallbackAPIVersion.VERSION2是 paho-mqtt 2.0 版本引入的写法用 1.x 版本的老代码跑在新库上会报警告回调函数的参数也会变。在自己项目里写新代码我建议直接用 VERSION2省得以后升级库还要改代码。另外客户端里同样可以设置遗嘱消息。连接成功后如果程序进程被 kill 掉Broker 就会在指定主题上发布遗嘱内容client.will_set(device/status, payloadoffline, qos1, retainTrue)这个功能在监控设备在线状态时极其好用我之前做平板端 UI 时网关一旦断开界面几乎立刻跳转到了离线状态比轮询检测快得多。4.4 QoS 和 retain消息不丢但会重复设计要提前想好把 QoS 和 retain 放一起说因为它们在“消息可靠性设计”里是一对组合拳。QoS 解决的是“消息能不能到”retain 解决的是“新订阅者能不能拿到最近状态”。一个典型场景设备上报传感器数据每条数据都带时间戳订阅方拿到消息后发现 QoS 1 可能导致重复投递就要在业务层做一次去重。另一个场景设备开关状态这种东西如果只在变化时发一条消息新订阅者加入时只能从“下一个变化”开始等用 retain 可以让 Broker 永久保存主题下的最新一条消息新订阅者一进来就能拿到当前状态。我之前用 retain 存灯的状态App 断线重连后 UI 状态不会丢体验上升不少。但 retain 也要慎用。如果设备每 5 秒上报一次温度就不要开 retain否则消息会频繁覆盖浪费 Broker 存储。retain 只适合低频、有状态的数据比如“当前模式”“设备在线状态”。5. 配置文件进阶从密码认证到 TLS 加密5.1 多监听器配置1883 内网 8883 公网一个 mosquitto 进程可以同时监听多个端口对内用明文 1883对外用 TLS 加密的 8883两个监听器用相同或不同的认证策略都行。配置文件示意listener 1883 0.0.0.0 allow_anonymous false password_file /etc/mosquitto/passwd listener 8883 0.0.0.0 allow_anonymous false password_file /etc/mosquitto/passwd certfile /etc/mosquitto/certs/server.crt keyfile /etc/mosquitto/certs/server.key这样内网设备走 1883外网设备走 8883认证策略各自独立调整非常实用。5.2 使用自签证书做 TLS 加密通信在本地实验或内网场景申请 CA 机构签发的证书成本很高也不需要。用 openssl 生成自签 CA 和服务器证书就行。先生成 CA 私钥和根证书openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj /CNMy Test CA再生成服务器私钥和证书签名请求openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr -subj /CN192.168.1.10使用 CA 签发服务器证书openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 825 -sha256把server.crt和server.key放到 mosquitto 能读到的目录目录权限建议 700公私钥文件权限 600。然后在监听器配置里加上certfile和keyfile两项重启服务。客户端连接时需要指定 CA 证书来校验服务器身份mosquitto_sub -h 192.168.1.10 -p 8883 --cafile /path/to/ca.crt -t sensor/# -u mqtt_user -P 密码注意用自签证书时如果客户端没有指定 CA 证书或者 CA 证书与服务器端不一致TLS 握手会失败。调试的时候很烦但只要一步一步把证书链路理顺就会非常稳定。5.3 公网访问的运维建议别在公网裸奔自建 MQTT 服务器被公网扫描是一件迟早的事。即使有密码认证我也强烈建议至少做到以下三点第一不要使用默认端口 1883 对外改成不常见的端口能少掉一批自动化扫描流量。但这只是底层安全手段真正的安全靠认证和加密。第二强制启用 TLS并用强密码不要用 123456、mqtt 这类弱口令。第三有条件的话在云平台安全组或者路由器防火墙里对来源 IP 做白名单只允许固定 IP 网段访问这是最有效的防护手段。在使用场景上我还会单独建一个“对外服务”专属账号密码单独设置禁止这个账号订阅内部管理主题全部通过 ACL 限制好。数据处理上公网接入的设备数据和内网管理数据尽量分主题域隔离出问题排查范围才会清晰。6. 用 Docker 部署 mosquitto一条命令拉起来6.1 为什么推荐 Docker 部署如果你已经装了 Docker或者准备在服务器上同时跑多个应用我更推荐用容器方式部署 mosquitto。好处很直接环境隔离、升级方便、配置迁移容易不用操心 systemd 和软件源依赖。我之前在一台 Ubuntu 机器上同时跑 mosquitto、Node-RED、InfluxDB全部用 Docker Compose 管起来重启服务器之后一条命令拉起所有服务比手工维护 pid 脚本省心得多。6.2 最小化 Docker 配置先把镜像拉下来docker pull eclipse-mosquitto:2然后指定配置目录和数据目录挂载mkdir -p /opt/mosquitto/config /opt/mosquitto/data /opt/mosquitto/log chown -R 1883:1883 /opt/mosquitto注意eclipse-mosquitto 官方镜像默认以 UID 1883 运行 mosquitto 用户挂载目录的属主必须是 1883否则容器启动时会因为没有写权限直接挂掉。这是官方镜像最知名的坑之一。把第 3 章写的配置文件放到/opt/mosquitto/config/mosquitto.conf注意配置文件里的持久化路径和日志路径必须改成容器内路径/mosquitto/data和/mosquitto/logpersistence true persistence_location /mosquitto/data/ log_dest file /mosquitto/log/mosquitto.log启动容器docker run -d --name mosquitto \ -p 1883:1883 -p 8883:8883 \ -v /opt/mosquitto/config:/mosquitto/config \ -v /opt/mosquitto/data:/mosquitto/data \ -v /opt/mosquitto/log:/mosquitto/log \ --restartalways \ eclipse-mosquitto:2这样服务就起来了日志文件定期保留升级容器时直接 docker pull docker stop/rm 运行同样的命令所有配置和数据都在宿主机不丢。6.3 Docker Compose 管理复杂配置配置内容多起来后用 Docker Compose 维护更省心。docker-compose.yml大致长这样services: mqtt: image: eclipse-mosquitto:2 container_name: mosquitto restart: always ports: - 1883:1883 - 8883:8883 volumes: - /opt/mosquitto/config:/mosquitto/config - /opt/mosquitto/data:/mosquitto/data - /opt/mosquitto/log:/mosquitto/logdocker compose up -d即可拉起。配置文件有任何修改docker compose restart mqtt重载服务不影响其他容器。7. 常见问题排查与主题设计经验7.1 连接失败排查速查表做 MQTT 联调时失败信息和可能原因基本逃不出下面这张表现象可能原因解决方案Connection refused服务没起、端口被占用、监听了错误网卡systemctl status mosquitto、ss -lntp确认监听地址Connection refused: not authorised用户名密码错误、allow_anonymous false后匿名登录检查账号密码、客户端是否传了-u -PConnection Refused: not authorised (ACL)ACL 规则没放行该用户对主题的访问检查acl文件中用户与主题的匹配关系TLS handshake failure证书文件路径错误、证书与密钥不匹配、客户端未指定 CA检查certfile/keyfile路径客户端加--cafile能连上但收不到消息Topic 拼写不一致、通配符层级不匹配、发布端 QoS 0 且订阅端加入晚用mosquitto_sub -d调试验证消息流向最常见还是监听网卡问题。Ubuntu 上安装后没改配置外部设备访问 IP 会直接 Connection refused但本机 curl 或 sub 却正常。遇到这类现象第一反应查看ss -lntp看看 1883 到底监听的 127.0.0.1 还是 0.0.0.0。7.2 系统日志里挖出真实原因服务起不来的情况直接看日志永远比瞎猜快sudo journalctl -u mosquitto -f也可以用配置文件里的log_type指定日志级别error、warning、notice、information等。debug 时用log_type all能看到非常完整的协议交互日志但生产环境别开日志量太大会把磁盘写满。7.3 Topic 设计规范从一开始就避免混乱Topic 是最容易被忽视却又影响整个消息架构稳定性的点。我的设计习惯分为四点层级结构使用“项目/设备类型/设备ID/数据项”的格式例如factory/machine01/temperature避免中文和特殊字符。每个设备单独一个前缀配合 ACL 授权让设备只能访问自己的前缀。用status/、cmd/、data/区分消息类型订阅规则按类别管控。通配符仅在订阅端使用发布端禁止用#和发布否则消息会污染整个主题空间。举个实际例子我做过一个车间环境监测系统约 30 个传感器节点Topic 结构是factory/zone1/temp、factory/zone1/humidity、factory/zone2/temp这样。订阅factory//temp就能拿到所有区域的温度既能做曲线汇总也能按区域分别展示任何节点出问题都能快速定位到最小范围。7.4 面试题式的关键点Keep Alive、Clean Session、持久会话既然热搜词里时不时出现“mqtt面试题”就顺着把几个高频考点讲透。Keep Alive 是客户端在连接时声明的时间间隔单位秒客户端需要在这个间隔内至少发送一次消息否则 Broker 会认为连接已断开并触发遗嘱消息。Clean Session0 表示持久会话1 表示临时会话决定了断开重连后Broker 是否保留订阅关系和离线期间的 QoS 1/2 消息。如果设备在线状态的连贯性重要就用 Clean Session 0。另一个容易混淆的点是QoS 1 的重复消息在“客户端网络不稳定、重连恢复”时常见。你要在业务层对消息做唯一编号过滤比如每条消息带一个递增 ID接收端记录最近收到的 ID重复的直接丢弃。这就是“协议保证不丢不重但业务层要处理乱序和重复”这句话的真实含义。7.5 上线前的最后检查清单把服务从“能跑”变成“能稳定跑”我一般在正式接设备前过一遍这六项密码文件已启用并关闭匿名访问。ACL 规则已覆盖所有接入账号并按最小权限开放。1883 端口是否只在内网开放公网端口优先走 8883 加 TLS。系统防火墙已经放行了相应端口。Docker 部署确认了目录权限和持久化配置。对每台设备确认它的 Topic 命名与 ACL 匹配。想了一个晚上突然反应过来还应该检查设备的遗嘱主题是否被 ACL 放行否则断线时 Broker 发布遗嘱消息会被自己的 ACL 规则拦截在线状态监控会失效。写在最后的实践心得这几步跑下来你会得到一套带用户名密码认证、ACL 权限隔离、TLS 可选加密、日志可查的 MQTT 消息服务器设备能在局域网内稳定收发消息后续接数据库、接可视化面板都不会缺基础能力。我在实际使用中最深刻的体会是很多 MQTT 的问题不是协议多难而是配置细节太容易被忽略。监听地址、密码文件权限、ACL 放行范围、客户端参数漏传任何一个点踩到现象都是“连不上”或“收不到”而且报错信息往往很笼统。所以遇到问题别急着重启十次先把监听状态、日志、客户端调试输出三个环节串起来看通常十分钟就能定位。另外不管服务多小第一次上线就开启密码认证、关闭匿名访问这个习惯能帮你挡掉大部分扫描流量。自己玩的服务器是这样公司项目更是这样。
返回列表