Tauri应用中Rust调用tshark实时流数据到Svelte UI无响应问题
Tauri调用tshark:UI卡顿+数据未传递问题解决方案
问题描述
基于Tauri开发桌面应用,通过Rust调用tshark命令行工具,期望将tshark的实时输出传递到Svelte前端。当前编译无错误,但点击启动按钮后UI卡顿,后端已成功执行tshark并输出日志,但数据未传递到前端。
核心问题分析
- UI卡顿原因:
start_tshark命令的异步函数一直阻塞在读取tshark输出的循环中,前端await invoke("start_tshark")时,Promise始终无法resolve,导致UI线程被占用卡死。 - 数据未传递原因:前端在
invoke执行完成后才注册事件监听,但tshark持续运行导致invoke一直未结束,监听根本无法生效;同时原代码中使用output.textContent = event.payload会覆盖之前的输出,无法展示实时更新内容。
代码修改方案
1. Rust后端(main.rs)修改
将tshark的输出处理逻辑放到Tokio后台任务中,让start_tshark命令立即返回,避免阻塞前端:
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::process::Stdio; use std::sync::{Arc, Mutex}; use tauri::Manager; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Command, Child}; use lazy_static::lazy_static; // 全局保存tshark进程句柄,用于停止操作 lazy_static! { static ref TSHARK_CHILD: Arc<Mutex<Option<Child>>> = Arc::new(Mutex::new(None)); } #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}!", name) } #[tauri::command] async fn start_tshark(app: tauri::AppHandle) -> Result<(), String> { let mut child = Command::new("sudo") .args(["tshark", "-i", "any"]) .stdout(Stdio::piped()) .spawn() .map_err(|e| e.to_string())?; // 保存进程句柄 *TSHARK_CHILD.lock().unwrap() = Some(child); let child_ref = TSHARK_CHILD.lock().unwrap().as_mut().ok_or("Failed to get tshark process")?; let stdout = child_ref.stdout.take().ok_or("Failed to capture stdout")?; let reader = BufReader::new(stdout); let mut lines = reader.lines(); let main_window = app.get_window("main").ok_or("Main window not found")?; let child_clone = Arc::clone(&TSHARK_CHILD); // 启动后台任务处理tshark输出,不阻塞invoke返回 tokio::spawn(async move { while let Ok(Some(line)) = lines.next_line().await { if let Err(e) = main_window.emit("tshark-output", &line) { eprintln!("Failed to send event: {}", e); break; } } // 等待进程结束并清理句柄 if let Some(mut child) = child_clone.lock().unwrap().take() { if let Err(e) = child.wait().await { eprintln!("Tshark process error: {}", e); } } }); Ok(()) } // 新增停止tshark的命令 #[tauri::command] async fn stop_tshark() -> Result<(), String> { if let Some(mut child) = TSHARK_CHILD.lock().unwrap().take() { if let Err(e) = child.kill().await { return Err(format!("Failed to stop tshark: {}", e)); } if let Err(e) = child.wait().await { return Err(format!("Tshark stop error: {}", e)); } Ok(()) } else { Err("Tshark is not running".to_string()) } } fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![start_tshark, greet, stop_tshark]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }
依赖补充:在Cargo.toml中添加lazy_static依赖:
[dependencies] lazy_static = "1.4.0"
2. Svelte前端修改
提前注册事件监听,避免错过输出;修改输出逻辑为追加内容,同时实现停止按钮功能:
<script> import { onMount, onDestroy } from "svelte"; import { invoke } from "@tauri-apps/api/tauri"; import { listen } from "@tauri-apps/api/event"; import Greet from "../components/Greet.svelte"; let unlisten; let outputElement; onMount(async () => { // 提前注册事件监听,确保能接收所有实时输出 unlisten = await listen("tshark-output", (event) => { console.log("Received from Rust:", event.payload); // 追加内容而非覆盖 outputElement.textContent += `${event.payload}\n`; }); const startBtn = document.getElementById("start-tshark"); const stopBtn = document.getElementById("stop-tshark"); outputElement = document.getElementById("output"); startBtn.onclick = async () => { try { outputElement.textContent = ""; // 清空历史输出 await invoke("start_tshark"); } catch (err) { outputElement.textContent = `启动失败: ${err}`; } }; stopBtn.onclick = async () => { try { await invoke("stop_tshark"); outputElement.textContent += "\n已停止tshark捕获"; } catch (err) { outputElement.textContent += `\n停止失败: ${err}`; } }; }); onDestroy(() => { unlisten?.(); }); </script> <button id="start-tshark">启动Tshark捕获</button> <button id="stop-tshark">停止Tshark捕获</button> <pre id="output" bind:this={outputElement}></pre> <Greet />
修改后效果
- UI不再卡顿:
start_tshark命令立即返回,释放前端线程 - 实时接收输出:提前注册事件监听,能实时获取
tshark的每一行输出并展示 - 完善交互:实现停止按钮,可主动终止
tshark进程
内容的提问来源于stack exchange,提问作者chovy
相关产品推荐
相关产品推荐

