Tauri与WebSocket客户端无法同时运行的技术求助
解决Tauri与WebSocket客户端同时运行及通信问题
核心问题原因
Tauri的run()方法会独占主线程启动系统事件循环,不能将其放入Tokio后台任务执行——这是你当前方案失效的关键。必须让Tauri在主线程运行,WebSocket客户端作为后台任务依托Tokio runtime执行,同时建立两者的通信管道。
正确实现方案
1. 主线程与后台任务拆分
放弃tokio::main宏,手动初始化Tokio runtime,在Tauri的setup钩子中启动WebSocket客户端任务,确保WebSocket能访问到AppHandle。
2. 双向通信管道搭建
用Tokio的mpsc通道实现:
- WebSocket→Tauri:收到服务器消息后,通过Tauri的事件系统通知前端更新UI
- Tauri→WebSocket:通过自定义命令触发WebSocket发送消息
3. 心跳与重连机制
定时发送Ping帧维护连接,检测到断开后自动尝试重连,保证通信稳定性
完整示例代码
use tauri::{ CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, WindowBuilder, WindowUrl, command, AppHandle, State }; use futures_util::{SinkExt, StreamExt}; use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; use tokio::sync::{mpsc, Mutex}; use std::time::Duration; // 全局状态:存储WebSocket发送通道与连接状态 #[derive(Clone)] struct WebSocketState { sender: Mutex<Option<mpsc::Sender<Message>>>, connected: Mutex<bool>, } async fn run_websocket_client(app_handle: AppHandle, tx: mpsc::Sender<Message>) { let url = "ws://localhost:2794"; let mut connected = false; loop { if !connected { println!("尝试连接WebSocket..."); match connect_async(url).await { Ok((mut socket, _)) => { println!("WebSocket连接成功"); connected = true; *app_handle.state::<WebSocketState>().connected.lock().await = true; // 发送认证消息 let auth_msg = Message::Text(r#"{"event": "authentication", "value": "key1"}"#.to_string()); if let Err(e) = socket.send(auth_msg).await { eprintln!("认证消息发送失败: {:?}", e); connected = false; continue; } // 拆分读写流 let (mut write, mut read) = socket.split(); // 后台任务:处理服务器消息 let app_clone = app_handle.clone(); tokio::spawn(async move { while let Some(msg) = read.next().await { match msg { Ok(message) => { println!("收到消息: {}", message); // 触发前端事件更新UI app_clone.emit_all("websocket-message", message.to_string()).unwrap(); } Err(e) => { eprintln!("WebSocket读取错误: {:?}", e); break; } } } // 标记连接断开 *app_clone.state::<WebSocketState>().connected.lock().await = false; connected = false; }); // 后台任务:处理Tauri发来的消息 let mut rx = tx.clone(); tokio::spawn(async move { while let Some(msg) = rx.recv().await { if let Err(e) = write.send(msg).await { eprintln!("消息发送失败: {:?}", e); break; } } }); // 心跳任务:每30秒发送Ping tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(30)); while connected { interval.tick().await; if let Err(e) = write.send(Message::Ping(vec![])).await { eprintln!("Ping发送失败: {:?}", e); connected = false; break; } } }); // 等待连接断开或程序退出 tokio::select! { _ = tokio::signal::ctrl_c() => break, _ = async { while connected { tokio::time::sleep(Duration::from_secs(1)).await } } => {}, } } Err(e) => { eprintln!("WebSocket连接失败: {:?}", e); *app_handle.state::<WebSocketState>().connected.lock().await = false; // 5秒后重试连接 tokio::time::sleep(Duration::from_secs(5)).await; } } } else { tokio::time::sleep(Duration::from_secs(1)).await; } } } // Tauri命令:前端调用此方法发送WebSocket消息 #[command] async fn send_websocket_message(state: State<'_, WebSocketState>, message: String) -> Result<(), String> { let mut sender = state.sender.lock().await; match sender.as_ref() { Some(tx) => tx.send(Message::Text(message)).await.map_err(|e| e.to_string()), None => Err("WebSocket未连接".to_string()), } } fn main() { // 初始化Tokio runtime let runtime = tokio::runtime::Runtime::new().unwrap(); // 创建WebSocket通信通道 let (tx, rx) = mpsc::channel(100); tauri::Builder::default() .manage(WebSocketState { sender: Mutex::new(Some(tx)), connected: Mutex::new(false), }) .invoke_handler(tauri::generate_handler![send_websocket_message]) .setup(move |app| { let app_handle = app.handle(); // 在Tokio后台启动WebSocket客户端 runtime.spawn(async move { run_websocket_client(app_handle, rx).await; }); // 你的多窗口创建逻辑(可自行补充) // ... Ok(()) }) .system_tray(SystemTray::new().with_menu(SystemTrayMenu::new() .add_item(CustomMenuItem::new("show", "Show")) .add_item(CustomMenuItem::new("hide", "Hide")) .add_native_item(SystemTrayMenuItem::Separator) .add_item(CustomMenuItem::new("quit", "Quit")))) .on_system_tray_event(|app, event| match event { SystemTrayEvent::MenuItemClick { id, .. } => { let window_labels = app.state::<Vec<String>>(); for label in window_labels.iter() { if let Some(window) = app.get_window(label) { match id.as_str() { "show" => { window.show().unwrap(); window.set_focus().unwrap(); } "hide" => window.hide().unwrap(), "quit" => app.exit(0), _ => {} } } } } _ => {} }) .run(tauri::generate_context!()) .expect("Tauri应用启动失败"); }
关键细节说明
- 主线程优先级:Tauri必须占据主线程运行事件循环,WebSocket依托Tokio runtime在后台执行,避免阻塞
- 状态共享:通过Tauri的
manage方法存储WebSocket状态,实现命令与WebSocket任务的通信 - 双向通信:WebSocket收到消息后通过
emit_all触发前端事件,前端通过invoke调用命令发送消息 - 稳定性保障:内置心跳检测与自动重连逻辑,处理连接断开、消息发送失败等异常情况
内容的提问来源于stack exchange,提问作者Reece Harris
相关产品推荐
相关产品推荐

