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

Tauri应用中Rust调用tshark实时流数据到Svelte UI无响应问题

Tauri调用tshark:UI卡顿+数据未传递问题解决方案

问题描述

基于Tauri开发桌面应用,通过Rust调用tshark命令行工具,期望将tshark的实时输出传递到Svelte前端。当前编译无错误,但点击启动按钮后UI卡顿,后端已成功执行tshark并输出日志,但数据未传递到前端。

核心问题分析

  1. UI卡顿原因:start_tshark命令的异步函数一直阻塞在读取tshark输出的循环中,前端await invoke("start_tshark")时,Promise始终无法resolve,导致UI线程被占用卡死。
  2. 数据未传递原因:前端在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:43:14