You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 20:57:11