我用tauri app实现了一个根据端口号查看进程信息的客户端

684 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

前言

这两天了解了一下tauri,为了能更快地了解它,就用它做了一个小小的功能:通过输入的端口号查看计算机上的进程信息。这个需求衍生于开发人员经常会遇到端口号被某一个进程占用了,需要通过指定的命令查看目标进程:lsof -i :端口号,接下来就记录一下我是怎么使用tauri实现这个功能的。

初始化tauri项目

因为tauri依赖于rust,所以需要先安装好rust,在此基础上,我们根据官方文档使用cargo来创建tauri app,就两行命令:

cargo install create-tauri-app
cargo create-tauri-app

image-20221123203816481.png

这两行命令执行完毕后,通过clion或者idea导入项目。

在项目导入后,需要通过命令编译后才能启动,可以在项目文件夹所在的终端执行以下命令:

cargo tauri dev

接下来等待计算机编译完成后,会启动一个类似下面的tauri app,那么我们的项目初始化就成功了。

image-20221123204227765.png

添加新功能

  • 前端代码

为了能够在前端输入端口号,我们需要修改一下index.html,我们直接拷贝一下输入框和按钮的代码:

<div>
 <input id="port-input" placeholder="输入一个端口号..." />
 <button id="port-button" type="button">查询进程</button>
</div>

我们分别调整一下对应的id属性为port-inputport-button

下面我们还需要把main.js也做一定的修改,我们需要给新增的按钮加上click事件,另外就是获取输入框port-input元素:

const {invoke} = window.__TAURI__.tauri;
​
let portInputEl;
let greetInputEl;
let greetMsgEl;
​
async function greet() {
   // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
   greetMsgEl.textContent = await invoke("greet", {name: greetInputEl.value});
}
​
// 调用rust提供的方法获取进程列表
async function get_process_by_port() {
   invoke("get_process_by_port", {portparseInt(portInputEl.value)})
      .then((res) => {
           render_process_list(greetMsgEl, res);
      });
}
​
function render_process_list(element, res) {
 // TODO
 // 渲染列表
}
​
​
window.addEventListener("DOMContentLoaded", () => {
   // 获取input元素
   portInputEl = document.querySelector("#port-input");
   greetInputEl = document.querySelector("#greet-input");
   greetMsgEl = document.querySelector("#greet-msg");
   document
      .querySelector("#greet-button")
      .addEventListener("click", () => greet());
   // 监听click事件
   document.querySelector("#port-button")
      .addEventListener("click", () => get_process_by_port());
});
  • rust代码

rust代码中,我们需要实现通过调用shell命令的方式,获取查询出来的结果,并返回给前端。

我们先看一下在终端中执行的结果:

image-20221123205548696.png

上述脚步通过rust调用,代码如下:

use std::process::Command;
use serde::{Serialize, Deserialize};
​
// 构建结构体,用来包装结果
#[derive(Serialize, Deserialize, Debug)]
struct ProcessInfo {
   command: String,
   pid: String,
   user: String,
   fd: String,
   ip_type: String,
   device: String,
   size_off: String,
   node: String,
   name: String,
}
​
#[tauri::command]
fn get_process_by_port(port: isize-> Result<Vec<ProcessInfo>, String> {
   let output = Command::new("lsof")
      .arg("-i")
      .arg(format!(":{}", port))
      .output().unwrap();
   // 获取输出结果
   let shell_output = String::from_utf8(output.stdout).unwrap();
   // 根据换行符分割
   let vecVec<&str> = shell_output.split("\n").collect();
   let mut resultVec<ProcessInfo> = vec![];
   // 循环遍历,丢弃第一行title和最后一行空字符串
   for (i, v) in vec.iter().enumerate() {
       if i <= 0 || i == vec.len() - 1 {
           continue;
      }
       // 根据空格分割,把每一个字段对应的值找出来
       let valuesVec<&str> = v.split_whitespace().collect::<Vec<&str>>();
       // 包装成ProcessInfo对象并放进数组中
       result.push(
           ProcessInfo {
               command: values[0].into(),
               pid: values[1].into(),
               user: values[2].into(),
               fd: values[3].into(),
               ip_type: values[4].into(),
               device: values[5].into(),
               size_off: values[6].into(),
               node: values[7].into(),
               name: values[8].into(),
          }
      );
  }
   // 返回结果
   Ok(result)
}

因为我们通过shell命令返回的是一整串字符串,所以上述代码大部分都是在把字符串处理成目标对象。剩下的就是把get_process_by_port方法通过main注册到invoke_handler中:

fn main() {
   tauri::Builder::default()
      .invoke_handler(tauri::generate_handler![greet, get_process_by_port])
      .run(tauri::generate_context!())
      .expect("error while running tauri application");
}

编译运行

前后端代码都准备好后,我们开始通过cargo tauri dev命令执行项目编译,这个需要等待一段时间。当项目编译完毕启动后,界面如下:

image-20221123211005284.png 输入端口号8080,和我们在终端得到的结果一致,说明我们的第一个小功能成功了!

image-20221123211134492.png