快速上手
安装之后,代理服务器在三个端口上监听::1883 提供 TCP 上的 MQTT,:8883 提供 TLS 上的 MQTT,:8567 提供 Web 控制台以及 /mqtt 路径下 WebSocket 上的 MQTT。
打开 http://localhost:8567 并以管理员身份登录。默认账号是 admin / admin;首次登录后请立即在「个人设置」里更改密码。
创建第一个用户:名称、至少八位的密码,以及一条 ACL 规则。规则 $u/# 会让设备对自己的分支拥有完全访问权——控制台里就有一个「自己的分支」按钮专门用于此。
mosquitto_pub -h localhost -p 1883 -u sensor-42 -P s3nsorPass! \
-t 'sensor-42/temp' -m '21.5'
mosquitto_sub -h localhost -p 1883 -u sensor-42 -P s3nsorPass! \
-t 'sensor-42/#' -v
在 Linux 上安装
推荐走 APT 仓库:更新会随系统更新一起到来。
sudo wget -qO /usr/share/keyrings/elx-repo.gpg https://repo.um-d.ru/elx-repo.gpg
echo "deb [signed-by=/usr/share/keyrings/elx-repo.gpg] https://repo.um-d.ru stable main" | sudo tee /etc/apt/sources.list.d/elx-repo.list
sudo apt-get update
sudo apt-get install elxmqttbroker
软件包会把可执行文件装到 /opt/elxmqttbroker,创建一个不能登录的系统用户 elxmqtt,把数据和 config.json(权限 0600)放在旁边,并把 elxmqttbroker 服务设为开机启动。
systemctl status elxmqttbroker # 状态
sudo systemctl restart elxmqttbroker # 重启
journalctl -u elxmqttbroker -f # 日志
该单元用 ProtectSystem=strict 加了约束:只有代理服务器自己的目录可写。默认情况下控制台只在 127.0.0.1:8567 监听并经由 nginx 发布,而 MQTT 的 1883 和 8883 直接对外——那是另一套协议,不需要反向代理。
Upgrade 和 Connection 两个头,并把 proxy_read_timeout 设得宽裕些:/ws/live 和 /mqtt 都是长时间保持的 WebSocket 连接。在 Windows 上安装
通常使用安装程序 elxmqttbroker-setup.exe:它把程序放进 Program Files,把数据放进 C:\ProgramData\ELX MQTT Broker,可选地注册 ELXMQTTBroker 服务随系统启动,并在防火墙中放行 1883、8883 和 8567 端口。界面为俄语和英语,需要管理员权限。
ProgramData 而不是程序旁边:代理服务器会从控制台改写 config.json,而服务不应该往 Program Files 里写东西。卸载时该文件夹会保留——里面存着用户、他们的权限和消息。便携方式是单个 elxmqttbroker.exe:放进单独的文件夹运行,旁边就会出现 config.json、data 目录和一张自签名 TLS 证书。
elxmqttbroker.exe # 用默认设置运行
elxmqttbroker.exe -web :9000 # 让控制台监听别的端口
elxmqttbroker.exe -version # 版本与构建信息
便携版也可以手工当作服务来管理,用的是与安装程序相同的命令:
elxmqttbroker.exe -service install -config "C:\ProgramData\ELX MQTT Broker\config.json" -data "C:\ProgramData\ELX MQTT Broker\data"
elxmqttbroker.exe -service start
elxmqttbroker.exe -service stop
elxmqttbroker.exe -service uninstall
配置
config.json 是代理服务器唯一读取的文件。它在首次启动时生成,只保存启动前必须知道的内容:地址、TLS 和管理员账号。
{
"dataDir": "data",
"auth": { "username": "admin", "password": "admin" },
"mqtt": { "tcp": ":1883", "tls": ":8883" },
"web": { "addr": ":8567", "tlsAddr": "" },
"tls": { "cert": "", "key": "", "selfSigned": true,
"hosts": ["localhost", "127.0.0.1", "::1"] },
"logLevel": "info",
"logFormat": "text",
"persistence": true
}
明文的 password 会在首次启动时变成 passwordHash 并从文件中抹去。其余一切——协议上限、SMTP、桥接、规则、API 令牌、MQTT 用户——都在控制台里配置并保存在 data/users.json 中;这些都不需要重启。
命令行选项的优先级高于配置文件:
| 选项 | 作用 |
|---|---|
| -config | config.json 的路径 |
| -data | 数据目录 |
| -mqtt | TCP 上 MQTT 的监听地址 |
| -mqtt-tls | TLS 上 MQTT 的监听地址 |
| -web | Web 控制台的监听地址 |
| -log | 日志级别 |
| -version | 打印版本并退出 |
config.json 里有管理员密码的哈希、SMTP 密码以及外部认证来源的凭据。该文件的权限应设为 0600。用户与权限
这里有两套体系,切勿混淆。管理员是在 config.json 中定义的唯一账号,只管理控制台:没有角色划分,没有注册,进去之后就拥有全部权限。MQTT 设备是另一份账号列表:1 到 64 个字符的名称、密码、启用开关以及主题权限。
一条 ACL 规则由主题过滤器、访问方式和判定三部分组成:
| 字段 | 取值 |
|---|---|
| 过滤器 | 普通的 MQTT 过滤器:sensors/+/temp、dev/#、$u/# |
| 访问方式 | read、write 或 readwrite |
| 判定 | 允许或拒绝 |
规则自上而下检查,命中第一条即生效,并且默认一律拒绝:没有明确的允许就没有访问权。顺序也是配置的一部分,因此规则可以用鼠标拖动,也可以用方向键移动。
过滤器中的 $u 会展开为发起连接的客户端名称。仅 $u/# 一条规则就能把每台设备锁在自己的分支里,免去按名字逐条编写。
密码以 PBKDF2-HMAC-SHA256 存储,迭代 21 万次并加随机盐。会话令牌和 API 令牌同样只保存哈希。
MQTT 认证
凭据来源在「设置 → 认证」里选择,同一时间只有一个生效。切换在运行中完成:先用「测试」按钮验证新来源,只有它有响应,代理服务器才会切换,并断开客户端让它们重新认证。如果该来源不可达,则什么都不会改变。
| 来源 | 适用场合 |
|---|---|
internal | 账号在控制台里管理——默认选项 |
mysql / mariadb | 设备已经存在于你系统的数据库里 |
sqlite | 同上,只不过数据库是一个文件 |
csv | 简单的名单;文件变化时会重新载入 |
jwt | 设备用签名令牌代替密码 |
http | 每次连接都由你的服务来裁决 |
MySQL 和 MariaDB 的表结构沿用 EMQX:
CREATE TABLE mqtt_user (username VARCHAR(128) PRIMARY KEY, password VARCHAR(255));
CREATE TABLE mqtt_acl (username VARCHAR(128), action VARCHAR(16),
permission VARCHAR(16), topic VARCHAR(255));
-- action: publish | subscribe | pubsub; permission: allow | deny
INSERT INTO mqtt_user VALUES ('dev', SHA2('devpass',256));
INSERT INTO mqtt_acl VALUES ('dev','pubsub','allow','dev/#');
"mqttAuth": {
"backend": "mariadb",
"sql": {
"dsn": "mqtt:mqttpass@tcp(127.0.0.1:3306)/mqtt",
"passwordQuery": "SELECT password FROM mqtt_user WHERE username = ${username} LIMIT 1",
"aclQuery": "SELECT action, permission, topic FROM mqtt_acl WHERE username = ${username}",
"passwordHash": "sha256"
}
}
${username} 和 ${clientid} 是作为查询参数绑定的,而不是字符串拼接,因此不可能发生注入。passwordHash 支持 plain、sha256、sha512、bcrypt 和 pbkdf2。
JWT 放在密码或用户名字段里。支持 HS256/384/512 和 RS256/384/512;会校验签名、exp 和 nbf,也可以选择校验 iss、aud 以及你自定义的任何声明。权限取自 acl 声明,两种 EMQX 格式都可以,而 is_superuser 会完全免去检查。
"alg": "none" 伪造的令牌通不过。HTTP 来源会收到 {username, password, clientid, peerhost},并以 {"result": "allow", "acl": […]} 回应。空的 200 视为允许;deny、401、403、超时以及任何错误都视为拒绝——一个连不上的授权服务不该变成一扇敞开的门。响应会缓存 cacheSeconds 秒。
规则:重写、自动订阅、限流
主题重写会在运行中替换主题名称。一条规则由用于低成本初筛的 MQTT 过滤器、一个正则表达式,以及带 $1…$9 分组的目标模板组成:
| 何时 | 过滤器 | 正则表达式 | 新主题 |
|---|---|---|---|
| 始终 | raw/# | ^raw/(.+)$ | cooked/$1 |
顺序与 EMQX 一致:先重写,后检查 ACL——权限是针对消息实际流经的主题来校验的。UNSUBSCRIBE 与 SUBSCRIBE 走同一套重写,否则客户端将永远无法取消订阅。
自动订阅会在连接建立后立即创建订阅,客户端无需发送 SUBSCRIBE。一条 %c/# 或 %u/# 的规则,就能让每个客户端拥有自己的分支,完全不必手工配置。
主题限流适用于客户端整体表现正常,只是某个特定主题来得太频繁的情况:
| 字段 | 作用 |
|---|---|
| 主题模式 | 普通的 MQTT 过滤器:sensors/+/temp |
| 动作 | 稀释到指定频率,或整条丢弃 |
| 每秒消息数 | 目标频率;允许小数(0.2 表示每五秒一条) |
| 统计维度 | 按客户端与主题,或按主题整体 |
洪泛控制在「设置 → 代理服务器」里配置,按连接生效:每秒消息数、消息突发量、每秒字节数、字节突发量。0 表示不限。超出限制不会断开连接:代理服务器会在下一次读取套接字前停顿,TCP 关闭窗口,发送方自己就慢下来了。
延迟发布
向 $delayed/{秒数}/{主题} 发布的消息会被代理服务器暂存,到期后再交给订阅者。格式与 EMQX 兼容。
mosquitto_pub -t '$delayed/60/sensors/alarm' -m '一分钟后'
间隔从 1 秒到 4 294 967(约 49 天)。确认会立即返回:代理服务器担保的是收到,而不是存在订阅者。分辨率为 1 秒。
队列可在「规则 → 延迟消息」中查看:主题、载荷、倒计时,并可逐条或一次性全部取消。消息可跨重启保留,代理服务器停机期间到期的消息会在启动后立刻发出。
代理之间的桥接
代理服务器可以作为客户端连接到另一台 MQTT 代理服务器,并双向转发主题。配置位于「设置 → 桥接」:远端地址(端口可省略——默认 1883,TLS 下为 8883)、账号、TLS、协议版本和规则。
| 规则字段 | 含义 |
|---|---|
| 过滤器 | 转发哪些主题 |
| 方向 | out、in 或 both |
| QoS | 跨桥接的投递质量 |
| 前缀 | 在各自一侧添加什么前缀 |
具备回环保护、自动重连、版本 5 下的 No Local(没有回声也没有重复),控制台里还有状态和计数。桥接工作在代理服务器层面,而不是作为一个单独的客户端。
REST API
控制台用会话 Cookie 调用自己的 API;外部系统则使用「设置 → API」里的 Bearer 令牌。令牌只显示一次,保存的只有它的哈希。令牌也可以只读发放——那样就只有 GET、HEAD 和 OPTIONS 能通过。
curl -H "Authorization: Bearer TOKEN" http://localhost:8567/api/stats
典型场景是:你的系统开通一台设备,同时给它授予访问代理服务器的权限。
# 创建
curl -X POST http://localhost:8567/api/users \
-H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
-d '{"username":"sensor-42","password":"s3nsorPass!","enabled":true,
"acl":[{"filter":"$u/#","access":"readwrite","allow":true}]}'
# 修改密码或权限(只改所发送的字段)
curl -X PATCH http://localhost:8567/api/users/sensor-42 \
-H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
-d '{"password":"newPass12345","enabled":false}'
# 删除
curl -X DELETE http://localhost:8567/api/users/sensor-42 \
-H "Authorization: Bearer TOKEN"
密码永远不会被返回。错误统一以 {"error":{"code":"…","message":"…"}} 的形式给出:duplicate、reserved、invalid_input、not_found、read_only、unauthorized。
改动立即生效:修改密码、调整权限或执行删除时,代理服务器会自行切断该用户当前的连接,让新规则马上起作用,而不必等到下次重连。
控制台的整套 API 都可用:统计与历史、客户端、订阅、保留消息、发布、事件、用户、设置、桥接、规则、延迟消息、导入与导出。
存储与重启
当 persistence: true 时,状态保存在 data/broker.db 中——WAL 模式的 SQLite,纯 Go 实现,不用 CGO。重启后仍然保留的是:
- 保留消息——一有变化就立即写入,而不是按定时器。超过消息过期时间的会在启动时清理。
- 持久会话——订阅、QoS 1/2 的离线队列以及未完成的握手。每十秒以及关闭时保存一次。清理会话不保存:按规范,它们随连接一起消失。
- 延迟消息——代理服务器停机期间到期的,会在启动后立刻发出。
启动时日志里会出现类似 state restored retained=128 sessions=7 subscriptions=34 delayed=2 的一行。同在 data/ 目录下的还有 users.json(账号、规则和控制台设置,写入是原子的)以及自签名证书。
已知限制
- 不支持集群:这款代理服务器按单进程设计。
- 桥接支持 QoS 0 和 1;QoS 2 会降级为 1。
- 协议中虽有 SCRAM 之类的增强认证,但内置来源并未提供,会返回
0x8C Bad authentication method。 - 频率限制按连接而非按用户生效:一个客户端开十条连接,就会得到十份额度。
- 机器健康状况只在 Linux 和 Windows 上计算;在 macOS 和 BSD 上,该卡片会老实显示「无数据」。