当前位置: 首页 > news >正文

Rust+ Tauri实现漂亮小巧的Mqtt客户端工具--AtomMQTT Client 实现详解

基于 Rust + Tauri 的桌面 MQTT 调试客户端
项目开源地址:https://gitcode.com/qq8864/atomMqtt


1. 概述

AtomMQTT Client 是一个跨平台桌面 MQTT 调试工具,使用Tauri v1框架构建。其核心架构为:

┌─────────────────────────────────────┐ │ Web 前端 │ │ HTML + CSS (Tokyo Night) + JS │ │ ───────── Tauri IPC ──────────▶ │ ├─────────────────────────────────────┤ │ Rust 后端 │ │ rumqttc MQTT 客户端 │ │ Tokio 异步事件循环 │ │ Tauri 命令处理层 │ └─────────────────────────────────────┘

技术栈:

层次技术版本
桌面框架Tauri1.6
后端语言Rust2021 edition
MQTT 协议rumqttc0.24
异步运行时Tokio1.36 (full)
序列化serde + serde_json1.0
前端原生 HTML/CSS/JS

项目开源地址:https://gitcode.com/qq8864/atomMqtt

2. Rust 后端实现

2.1 数据模型

MqttMessage — 消息结构
#[derive(Debug, Clone, Serialize)]pubstructMqttMessage{pubtopic:String,// 消息主题pubpayload:String,// UTF-8 载荷文本pubpayload_hex:String,// Hex 编码载荷(用于二进制数据查看)pubqos:u8,// 服务质量 (0/1/2)pubretain:bool,// 保留消息标志pubtimestamp:String,// 接收时间(毫秒精度)pubpacket_id:Option<u16>,// MQTT 包 ID}

每条接收到的消息会同时保存UTF-8 文本Hex 编码两种形式的载荷,前端可在 UI 中切换显示。

ConnectionStatus — 连接状态
pubstructConnectionStatus{pubconnected:bool,pubhost:String,pubport:u16,pubclient_id:String,pubconnected_at:Option<String>,}
SubscriptionInfo — 订阅信息
pubstructSubscriptionInfo{pubtopic_filter:String,pubqos:u8,}

2.2 全局状态管理

使用 Tauri 的State管理模式,通过AppState结构体管理所有运行时状态:

pubstructAppState{pubclient:Mutex<Option<AsyncClient>>,// MQTT 客户端句柄pubmessages:Arc<Mutex<Vec<MqttMessage>>>,// 消息缓冲区pubconnected:Arc<AtomicBool>,// 连接标志pubhost:Mutex<String>,pubport:Mutex<u16>,pubclient_id:Mutex<String>,pubconnected_at:Arc<Mutex<Option<String>>>,pubsubscriptions:Mutex<Vec<SubscriptionInfo>>,}

设计要点:

  • AsyncClientMutex<Option<...>>包装,支持连接断开和重建
  • messagesconnected使用Arc跨线程共享(前端轮询线程与 MQTT 事件循环线程)
  • AtomicBool用于connected标志——无锁读取,性能最优
  • 消息缓冲区上限10,000 条,避免内存泄漏

2.3 MQTT 连接与管理

连接流程使用rumqttc库的AsyncClientAPI:

asyncfnconnect(state:State<'_,AppState>,host:String,port:u16,client_id:String,username:Option<String>,password:Option<String>,clean_session:Option<bool>,)->Result<String,String>

步骤:

  1. 断开旧连接:如果已有连接,先优雅断开
  2. 创建 MQTT 选项:设置 Keep-Alive(30秒)、Clean Session、可选的用户名密码认证
  3. 创建客户端AsyncClient::new(options, 100)— 100 为消息队列容量
  4. 存储客户端句柄:保存到AppState.client供 publish/subscribe 使用
  5. 启动事件循环tokio::spawn一个异步任务持续处理 MQTT 事件
事件循环处理

在后台线程中循环调用eventloop.poll().await

事件类型处理逻辑
ConnAck连接成功 → 设置connected = true,记录连接时间
Publish收到消息 → 生成MqttMessage并存入缓冲区
Disconnect服务端断开 → 设置connected = false,退出循环
PingRespKeep-Alive 响应 → 忽略
错误记录日志,等待 3 秒后重试(容忍瞬态网络错误)

2.4 Tauri 命令层

共注册8 个 Tauri 命令,前端通过window.__TAURI__.tauri.invoke()调用:

命令功能参数返回值
connect连接 Brokerhost, port, client_id, username?, password?, clean_session?连接成功信息
disconnect断开连接()
publish发布消息topic, payload, qos, retain()
subscribe订阅主题topic_filter, qos()
unsubscribe取消订阅topic_filter()
get_messages获取消息列表Vec<MqttMessage>
clear_messages清空消息()
get_connection_status获取连接状态ConnectionStatus
get_subscriptions获取订阅列表Vec<SubscriptionInfo>

每个命令的核心模式:

#[tauri::command]asyncfncommand_name(state:State<'_,AppState>,...args)->Result<...,String>{// 1. 从 state 获取客户端句柄(加锁)letclient=state.client.lock()?.as_ref().ok_or("Not connected")?.clone();// 2. 执行操作client.do_something(...).await.map_err(|e|e.to_string())?;// 3. 更新状态Ok(...)}

ResultErr(String)会自动传递给前端 JavaScript 的catch块。


3. 前端实现

3.1 HTML 布局

采用经典的左-右两栏布局:

┌─ Title Bar ──────────────────────────────────┐ │ ◈ AtomMQTT Client ● 在线 v1.0.0 │ ├──────────┬────────────────────────────────────┤ │ Sidebar │ Tabs: [发布] [订阅] [消息日志] │ │ │ │ │ 连接设置 │ ── 发布 Tab ── │ │ Broker │ 主题: [__________] │ │ 客户端ID │ 载荷: [__________] │ │ 用户名 │ QoS: [v] 保留: [x] [发布] │ │ 密码 │ │ │ [连接] │ ── 订阅 Tab ── │ │ [断开] │ 过滤器: [___] QoS: [v] [订阅] │ │ │ 活跃订阅列表 │ │ 状态: 在线│ │ │ 时间: ...│ ── 消息日志 Tab ── │ │ │ [自动滚动] [Hex] [清空] │ │ │ 2026-05-28 12:34:56.789 │ │ │ test/topic │ │ │ hello world │ ├──────────┴────────────────────────────────────┤ │ Status Bar: ● 已连接 就绪 │ └───────────────────────────────────────────────┘

3.2 样式系统 (Tokyo Night 主题)

使用 CSS 自定义属性定义配色方案,灵感来自 Tokyo Night 主题:

:root{--bg-primary:#1a1b26;--bg-secondary:#24253a;--bg-tertiary:#2d2e42;--text-primary:#e2e3eb;--accent:#7aa2f7;--success:#9ece6a;--danger:#f7768e;}

主题特性:

  • 深色背景 + 高对比度文字,适合长时间调试使用
  • 等宽字体栈(Cascadia Code → Fira Code → JetBrains Mono → Consolas)
  • 平滑动画和圆角设计
  • 响应式布局,最小宽度 680px

3.3 JavaScript 逻辑

通信机制

前端通过window.__TAURI__.tauri.invoke()与 Rust 后端通信:

const{invoke}=window.__TAURI__.tauri;// 调用 Rust 命令constresult=awaitinvoke('connect',{host:'127.0.0.1',port:1883,clientId:'my-client',// ...});
状态轮询

采用轮询模式(而非 WebSocket/SSE)获取运行状态和消息:

functionstartPolling(){pollTimer=setInterval(pollStatus,1000);// 每秒轮询}asyncfunctionpollStatus(){// 1. 获取连接状态conststatus=awaitinvoke('get_connection_status');setConnected(status.connected);// 2. 获取新消息constmsgs=awaitinvoke('get_messages');updateLog(msgs);}

设计原因:Tauri v1 的 invoke 调用延迟极低(<1ms),轮询 1 秒间隔不会产生任何可感知的性能开销,且实现简单可靠。对于 MQTT 调试场景,消息延迟 1 秒以内完全可接受。

消息日志增量更新
letprevMsgCount=0;functionupdateLog(msgs){if(msgs.length===prevMsgCount)return;// 无新消息,跳过conststartIdx=prevMsgCount;constnewMsgs=msgs.slice(startIdx);// 仅处理新增消息for(constmsgofnewMsgs){constentry=document.createElement('div');// 构建 DOM 元素logContainer.appendChild(entry);}prevMsgCount=msgs.length;}
快捷键支持
快捷键功能
Ctrl+Enter快速发布(publish 按钮)
Escape取消当前输入框焦点
消息条双击切换 Hex/UTF-8 视图

4. 构建与部署

4.1 构建流程

推荐使用项目自带的构建脚本:

$ build.bat

脚本执行步骤:

  1. cargo build --release— 编译 Rust 后端 + Tauri 打包前端静态资源
  2. fix_pe.cmd— 自动修复 PE 头(仅 rust-lld 链接器需要,见 4.4 节)
  3. 复制target/release/tauri-mqtt-client.exedist/AtomMQTT-Client.exe

Tauri 在编译过程中会自动:

  1. 编译 Rust 后端 →tauri-mqtt-client.exe
  2. 读取tauri.conf.json中的distDir: "public"→ 将public/目录打包为静态资源
  3. 嵌入 Windows 资源(图标、版本信息)
  4. 输出最终的target/release/tauri-mqtt-client.exe

4.2 Tauri 配置 (tauri.conf.json)

关键配置项:

{"build":{"distDir":"public",// 前端静态文件目录"devPath":"public"// 开发模式下也直接使用静态文件},"package":{"productName":"AtomMQTT Client","version":"1.0.0"},"tauri":{"allowlist":{"shell":{"open":true}// 允许打开外部链接},"windows":[{"title":"AtomMQTT Client","width":1080,"height":800,"minWidth":680,"minHeight":500,"center":true}]}}

4.3 Windows 子系统设置 — 消除 DOS 窗口

默认 Rust 编译 Windows 程序时使用控制台子系统(Subsystem=3),导致程序启动时会同时弹出一个 DOS 控制台窗口。对于桌面 GUI 应用,需要改为GUI 子系统(Subsystem=2)

main.rs顶部添加:

#![windows_subsystem ="windows"]

这行代码告诉链接器:这是一个 Windows GUI 应用,无需分配控制台。Tauri 文档也推荐所有正式发布的桌面应用加上此属性。

4.4 PE 头损坏修复 — 解决"此版本与 Windows 不兼容"

问题现象

在 Windows 上使用rust-lld(LLVM LLD 链接器,Rust 工具链自带)替代 MSVClink.exe链接时,生成的.exe文件的DOS 头中的e_lfanew字段被写为 0。该字段是 PE 文件格式的入口指针——它指向真正的 PE 签名(PE\0\0)在文件中的偏移量。一旦为零,Windows 加载器无法定位 PE 头,就会报告:

“此版本与正在运行的 Windows 版本不兼容”

PE 文件结构示意
┌─ DOS Header (64 bytes) ──────────────────┐ │ ... │ │ e_lfanew = 0x78 ← 指向 PE 签名偏移 │ │ ... │ ├─ DOS Stub ────────────────────────────────┤ │ "This program cannot be run in DOS mode" │ ├─ PE Signature ────────────────────────────┤ │ "PE\0\0" ← Windows 从此处加载 │ ├─ COFF / Optional Headers ─────────────────┤ │ ... │ └───────────────────────────────────────────┘
根本原因

rust-lld 在生成 PE 文件时会写入错误的值到e_lfanew字段(偏移 0x3C 处,4 字节)。这在rust-lld的多个版本中均有出现,是 LLVM LLD 的一个已知问题。

解决方案:运行时修复

build.rs(Tauri 构建脚本)中添加一个PE 头修复步骤:编译完成后扫描生成的.exe文件,找到PE\0\0签名在文件中的实际偏移量,然后将正确的值写回e_lfanew字段。

build.rs中生成修复脚本的核心逻辑:

fnmain(){println!("cargo:rerun-if-changed=build.rs");// 读取编译后的 exe 文件letexe_path=std::env::current_dir().unwrap().join("target\\release\\tauri-mqtt-client.exe");// 生成修复脚本 fix_pe.cmdletscript=format!("powershell -Command \"$b=[System.IO.File]::ReadAllBytes('{}'); ... \"",exe_path.display());std::fs::write("fix_pe.cmd",script).unwrap();}

修复脚本运行后,Windows 加载器能够正确读取 PE 头,应用正常启动。

构建修复前后对比
指标修复前修复后
启动是否弹 DOS 窗口❌ 弹出控制台窗口✅ 无窗口
是否可运行❌ “版本不兼容” 错误✅ 正常启动
构建工具cargo buildbuild.bat
额外步骤build.rs 生成 → fix_pe.cmd 执行

5. 文件结构

tools/tauri-mqtt-client/ ├── Cargo.toml # Rust 依赖配置 ├── build.rs # Tauri 构建脚本(含 PE 头修复代码生成) ├── build.bat # 一键构建脚本(编译 → 修复 PE → 复制到 dist) ├── fix_pe.cmd # PE 头修复脚本(由 build.rs 自动生成) ├── tauri.conf.json # Tauri 应用配置 ├── .cargo/ │ └── config.toml # Rust 链接器配置(rust-lld) ├── icons/ │ ├── icon.ico # Windows 程序图标 │ └── icon.png # PNG 图标 ├── public/ # 前端静态文件(distDir) │ ├── index.html # 主页面 │ ├── styles.css # 样式表(640 行) │ └── script.js # 前端逻辑(353 行) ├── src/ │ └── main.rs # Rust 后端(323 行) ├── dist/ # 构建产出 │ └── AtomMQTT-Client.exe # 可执行文件 └── install.bat # Windows 安装脚本

6. 与同等工具的比较

特性AtomMQTT ClientMQTTX (Electron)mosquitto_sub (CLI)
二进制体积~7 MB~120 MB~500 KB(但不含 GUI)
内存占用~40 MB~200 MB
启动速度<500ms~3s瞬发
GUI 框架Tauri (系统 WebView)Electron (Chromium)
QoS 支持0/1/20/1/20/1/2
Hex 视图
主题Tokyo Night 深色可切换

AtomMQTT Client 的核心优势在于极低的资源占用快速的启动速度,利用了操作系统内置的 WebView2 Runtime,无需捆绑 Chromium。


7. 总结

AtomMQTT Client 是一个轻量、高效、美观的 MQTT 桌面调试工具:

  • Rust 后端通过rumqttc实现完整的 MQTT 3.1.1 协议支持
  • Tauri 框架提供原生桌面体验,无需 Electron 的臃肿
  • 原生前端(HTML/CSS/JS)零依赖,启动即用
  • Tokyo Night 深色主题适合开发者长时间使用
  • 双视图消息日志(UTF-8 / Hex)方便调试二进制协议

整个项目约1,300 行代码(Rust 323 行 + JS 353 行 + CSS 640 行),体现了 Rust + Tauri 栈构建桌面应用的简洁与高效。

http://www.jsqmd.com/news/906591/

相关文章:

  • 为什么你的RAG系统总是答非所问?90%的人都踩了这个坑
  • 这次终于选对了!盘点2026年抢手爆款的一键生成论文工具
  • FPGA轻量级NTT故障检测架构设计与实现
  • 别再只会用`--trusted-host`了!手把手教你修复Windows Python的SSL证书验证问题
  • NPU模拟器搭建与深度学习硬件加速优化实践
  • 中小商家的客服神器!开源、免费、可私有部署——CRMChat 技术架构全拆解
  • 如何3秒获取百度网盘提取码:智能查询工具baidupankey终极教程
  • 告别调包侠:用Librosa从零处理音频信号,手把手教你提取MFCC和梅尔谱图
  • 当了leader才发现,大厂最吃香的,不是代码写得快的,也不是会拍马屁的,而是把AI办公用到极致的。
  • 树莓派复古点唱机DIY:融合装饰艺术与可编程LED的音乐播放器
  • Vulkan多线程追踪文件转单线程的实践指南
  • 2026年模拟炒股软件横评:5款实测对比,新手入门选哪个?
  • 酒店门锁V10SDK接口vb窗口-幽冥大陆(一百28)—东方仙盟
  • RAG技术栈全解:从Embedding模型到Milvus部署,7个核心组件撑起企业级知识库
  • 跨域请求测试
  • Go语言并发编程模式与实战技巧
  • 告别懵圈!用5个关键函数串起LwIP数据包的一生(STM32+FreeRTOS实战)
  • Python 文件与目录自动化实战:os、pathlib、shutil 从入门到精通
  • Arduino智能助眠音箱DIY:从DFPlayer模块驯服到PCB实战
  • 卖 LED 灯珠怎么找客户?下游灯具厂在哪里
  • Honor of Kings 2026.05.24 S43 [15.9][15.8]
  • XRootD在400Gbps高带宽下的性能优化与实践
  • 手把手配置Aurix Development Studio的lsl文件:让TC397的变量乖乖住进你指定的‘内存房间’
  • 8051 PDATA内存访问机制与Keil µVision仿真解析
  • Matlab simulink 仿真FOC专题--(Park变换)
  • 终极指南:如何在Mac上解锁QQ音乐加密音频,实现跨平台播放自由
  • macOS文件预览效率低?QuickLook插件集让您的工作流焕然一新
  • 中兴B860AV1.2刷机避坑指南:S905M-B线刷固件选择、短接失败排查与刷砖救回
  • 终极指南:如何免费重置Navicat Premium 17.x在macOS上的试用期
  • 新手教程使用 Python 快速调用 Taotoken 上的多款大模型