1| 2| 3| 4| 5| 6|Rust WebSocket 代理:Wilson Desktop 的三层解耦架构 — Wilson Desktop 7| 8| 37| 38| 39| 40|
41|
42| Wilson 43| / 44| 博客 45| / 46| Rust WebSocket 代理与三层解耦 47|
48| 49|
50|

Rust WebSocket 代理:Wilson Desktop 的三层解耦架构

51|
2026-07-30 · #Tauri #Rust #IPC #工程实践
52| 53|

v0.2 是 Wilson Desktop 的第一个架构里程碑。在需求明确(通用 Agent 桌面客户端、使用 Tauri v2)之后,第一个要解决的问题不是 UI 布局或功能细节,而是决定"后端通信的线程"。

54| 55|

这篇文章记录从"前端直连 WebSocket"到"Rust IPC 代理"的架构转变,以及在这个过程中发现的 Tauri 工程实践。

56| 57|

初始架构的问题

58| 59|

Wilson Desktop 的日常使用场景是:桌面应用(Tauri)→ WebSocket → Python Hermes Agent。v0.1 的原型采用了最直接的路径——前端 React 直接 new WebSocket() 连 Python 后端:

60| 61|
┌─────────────────┐ WS (127.0.0.1:9119) ┌────────────────────┐ 62|│ React (Tauri) │ ───────────────────────────────────► │ Python Hermes │ 63|│ useHermesWS() │ ◄─────────────────────────────────── │ │ 64|└─────────────────┘ stream └────────────────────┘
65| 66|

这个模式在技术验证阶段完全合理——最小路径,快速验证通信链路。但如果一直这么用下去,Tauri 就只是一个 WebView 容器,失去了桌面端框架的核心价值。

67| 68|

具体痛点:

69| 70| 75| 76|

三层解耦架构

77| 78|

v0.2 重构的核心思路:所有后端通信经过 Rust IPC 层。前端只通过 invoke() 收发消息,不感知后端的具体协议。

79| 80|
┌─────────────────┐ Tauri IPC ┌──────────────────────┐ ws://host:port/ws ┌────────────────────┐ 81|│ React │ ◄── events ─── │ Rust (BackendManager)│ ──────────────────────► │ Python Hermes │ 82|│ ChatView │ ── invoke() ──► │ tokio-tungstenite │ ◄────────────────────── │ │ 83|│ StatusBar │ │ 连接/消息/状态管理 │ stream └────────────────────┘ 84|└─────────────────┘ └──────────────────────┘
85| 86|

关键变更:

87| 88| 94| 95|

BackendManager 设计

96| 97|

Rust 层的核心是 BackendManager,它负责:

98| 99| 104| 105|

前端通过 4 个 Tauri 命令与 BackendManager 交互:

106| 107|
// Rust 侧 (commands.rs)
   108|#[tauri::command]
   109|async fn connect_backend(app, state, host, port) → Result
   110|#[tauri::command]
   111|async fn disconnect_backend(state) → Result
   112|#[tauri::command]
   113|async fn send_message(state, content) → Result
   114|#[tauri::command]
   115|async fn get_backend_status(state) → Result<BackendStatus>
   116|
   117|// 前端调用
   118|import { invoke } from "@tauri-apps/api/core";
   119|import { listen } from "@tauri-apps/api/event";
   120|
   121|await invoke("connect_backend", { host: "127.0.0.1", port: 9119 });
   122|
   123|const unlisten = await listen("backend-event", (event) => {
   124|  // event.payload.type ∈ ["status_change","message","stream_start","stream_chunk","stream_end","error"]
   125|});
126| 127|

流式消息透传的实现

128| 129|

流式消息是 Agent 交互的核心体验。在 Rust 层做代理意味着需要把 ws_loop 中的接收循环与 Tauri 的事件系统桥接起来。

130| 131|

关键实现细节:

132| 133|
// ws_loop 的核心循环 (tokio::select! 多路复用)
   134|loop {
   135|    tokio::select! {
   136|        // 从前端的 mpsc channel 接收消息,转发给 WS
   137|        Some(msg) = rx.recv() => {
   138|            ws_write.send(Message::Text(msg)).await?
   139|        }
   140|        // 从 WS 接收消息,解析后 emit 给前端
   141|        Some(ws_msg) = ws_read.next() => {
   142|            match ws_msg {
   143|                Ok(Message::Text(text)) => {
   144|                    handle_ws_message(&app, &text).await;
   145|                }
   146|                Ok(Message::Close(_)) => {
   147|                    // 更新状态 + 通知前端
   148|                    break;
   149|                }
   150|                Err(e) => {
   151|                    // 错误处理
   152|                    break;
   153|                }
   154|                _ => {}
   155|            }
   156|        }
   157|    }
   158|}
159| 160|

每条 WS 消息在 handle_ws_message 中解析为 JSON,根据 type 字段分发为不同的 BackendEvent 变体,通过 app.emit() 发送到前端。前端在 React 组件中通过 listen("backend-event", callback) 接收并更新 UI 状态。

161| 162|

协议抽象的前瞻

163| 164|

虽然 v0.2 只实现了 Hermes WebSocket 协议,但架构为后续的协议抽象预留了空间。v0.4 的目标是实现:

165| 166|
// 未来协议抽象 trait
   167|trait AgentBackend {
   168|    async fn connect(&self, config: BackendConfig) -> Result<()>;
   169|    async fn send_message(&self, content: &str) -> Result<()>;
   170|    fn stream_events(&self) -> BoxStream<AgentEvent>;
   171|    async fn disconnect(&self) -> Result<()>;
   172|}
173| 174|

届时 v0.2 的 BackendManager 将成为 HermesWSAdapter 的实现,新增后端只需要实现这个 trait。前端无需感知切换。

175| 176|

前端配套改造

177| 178|

架构变更不只影响 Rust 层,前端也做了对应的重构:

179| 180| 186| 187|

构建验证

188| 189|

重构完成后,完整构建通过:

190| 191|
pnpm tauri build --bundles dmg
   192|
   193|产物:
   194|  Wilson Desktop.app        → 11 MB
   195|  Wilson Desktop_0.1.0_aarch64.dmg → 3.6 MB
   196|
   197|验证:
   198|  pnpm tsc --noEmit      → 零错误
   199|  cargo check            → 零 warnings
   200|  vite build             → 23 modules, 199KB JS + 1.5KB CSS
201| 202|

Tauri 的 .app 保持了 11MB 的典型大小,新增的 tokio-tungstenite 依赖没有让包体膨胀。

203| 204|
205| 206|

这次重构的核心理念是:在做功能之前先做对架构。三层解耦让 Wilson Desktop 的每个层次只关注自己的职责——前端只做 UI,Rust 层管通信和后端生命周期,Python 层管 Agent 逻辑。

207| 208|

下一阶段(v0.3)将在这个架构上构建双面板布局和嵌入式终端。

209| 210|
211|
212| 213| 214| 215| 216| 217|