Rust 实战四 | Traui2+Vue3+Rspack 开发桌面应用:通配符掩码计算器

往期回顾

代码开源地址: github.com/0604hx/rust...通配符掩码计算器


学习一门编程语言,总归会往 GUI 上面琢磨一些想法,关于 Rust 的GUI 可以看我往期的文章:Rust 语言 GUI 用户界面框架汇总

今天终于到 Tauri 的实践环节啦,我们将使用 Tauri2+Vue3 开发一个简易小工具:通配符掩码计算器。

需求概述

为什么是通配符掩码计算器?

某天我看同事使用了一款小巧的软件,计算 IP 地址范围(如下图),觉得很有意思。寻思这个工具很实用,功能实现不难,很适合拿来做编程语言的练习项目。 上述截图是软件的其中一项功能,详细的介绍请见:推荐一款子网掩码计算神器

关于通配符掩码

通配符掩码(Wildcard Mask)是网络技术中用于匹配 IP 地址范围的特殊标识,常见于路由协议(如 OSPF、EIGRP)和访问控制列表(ACL)中。它与子网掩码的作用类似,但逻辑相反,核心是通过0 和 1 的组合来定义 "需要精确匹配的位" 和 "可忽略的位"。

通配符掩码的基本规则

通配符掩码由 32 位二进制数组成(对应 IPv4 地址),每一位的含义如下:

  • 0:表示该位必须精确匹配(与目标 IP 地址的对应位完全相同)。
  • 1:表示该位可忽略(不关心目标 IP 地址的对应位是 0 还是 1)。

简言之:0 = 必须匹配,1 = 任意匹配

参考模板

  • tauri-vue-template:Fully configured project template for Tauri and Vue 3 w/ TypeScript and CI.

实施环节

工具主要主要功能就是根据用户输入的IP地址通配符掩码,计算匹配的 IP 地址,同时也提供发送到记事本,即创建一个同目录下的temp.txt并写入相应的内容后调用系统的记事本打开。

交互界面我们直接抄作业,做了简单的布局调整。

基础 UI 框架

我们的用户界面是基于 Vue3+Naive UI 实现的,刚开始还用不到 Tauri,先把界面框架搭建出来。下面是代码的结构:

执行 pnpm i 安装依赖后,再通过pnpm ui运行 web 项目,即可看到如下界面。

加入 Tauri

前置要求

这里以windows为例,其他系统请见Tauri Prerequisites

Tauri 使用 Microsoft C++ 生成工具进行开发以及 Microsoft Edge WebView2。这两者都是在 Windows 上进行开发所必需的。

按照以下步骤安装所需的依赖项:

  1. 下载并安装Microsoft C++ 生成工具,在安装过程中,选中"使用 C++ 的桌面开发"选项;
  2. 安装 WebView 2,注意:该组件已安装在 Windows 10(从版本 1803 开始)和更高版本的 Windows 上。如果你正在这些版本之一上进行开发,则可以跳过此步骤。

使用 tauri-cli 改造项目

因为我们已经有了 web 项目,只需要加入 Tauri 相关依赖即可。

  1. 安装依赖:pnpm add -D @tauri-apps/cli@latest
  2. 通过 tauri 在现有的基础上初始化项目:pnpm tauri init
  3. 此时,我们的项目中创建一个 src-tauri 目录,其中包含了重要的 Tauri 配置文件。 关于 src-tauri 的几个重要文件:
  • tauri.conf.json 是 Tauri 的主要配置文件,它包含从应用程序标识符到开发服务器 URL 的所有内容,该文件也是 Tauri CLI 查找 Rust 项目的标记, 详见Tauri Config
  • capabilities/ 配置在 JavaScript 代码中运行使用的权限,详见tauri.app/security/
  • src/lib.rs 包含 Rust 代码和移动入口点(标有 #[cfg_attr(mobile, tauri::mobile_entry_point)] 的函数),之所以跟 main.rs 分开,是因为要编译为移动版本中的库。
  • src/main.rs 是桌面的主入口点,我们在 main 中运行 tauri_app_lib::run() 以使用与移动端相同的入口点,因此为了简单起见,不要修改此文件,而是修改 src/lib.rs

通过 Tauri 启动项目

我们通过 pnpm tauri dev 即可以 tauri 方式启动现有项目,请注意第一次执行该命令,需要一个比较漫长的资源下载过程。

不久之后,我们就看到 tauri 程序窗口🎉。

再看看 src-tauri,竟然有 3.96GB 之大😮。

异常情况

1、无法加载 mspdbcore.dll 如果执行过程中碰到LINK : fatal error LNK1171: 无法加载 mspdbcore.dll (错误代码: 1455),请检查Microsoft C++ 生成工具是否正确安装。 2、out of memory

3、Error calling dlltool 'dlltool.exe': program not found Windows 下 Tauri(Rust 编译)缺少交叉编译工具 的典型错误,原因是 Rust 在 Windows 上编译带有某些依赖的项目(尤其是用到 GNU 工具链)时,需要 dlltool.exe,而这个工具默认不会自带。

这个问题通常发生在:

  1. 你安装的是 x86_64-pc-windows-gnu 工具链(GNU 版本),不是 MSVC 版本
  2. 或者 Tauri 依赖的某个 crate 在构建时调用了 GNU 工具链(比如一些用 C 编写的库)
shell 复制代码
rustup install stable-x86_64-pc-windows-msvc
rustup default stable-x86_64-pc-windows-msvc

4、linker link.exe not found

请检查Microsoft C++ 生成工具是否正确安装,安装时勾选:

  • 使用 C++ 的桌面开发(Desktop development with C++)
  • 在右边的"安装详细信息"中确保勾选:
    • MSVC v143 或更高版本
    • Windows 10/11 SDK
    • C++ CMake 工具(可选)

tauri 打包

执行pnpm tauri build 即可。 最终产物 exe 为 8.2MB(其中前端打包后的体积为 0.3MB),相比 electron 的200MB可谓是瘦骨嶙峋😄。 如果是 Tauri 1.x,体积更小,基础功能在 5MB 内。

打包瘦身

shell 复制代码
[profile.release]
codegen-units = 1   # 减少代码生成单元以提高优化效果
lto = true          # 链接时优化(Link Time Optimization)
opt-level = "z"     # Prioritizes small binary size. Use `3` if you prefer speed,z to optimize for size, and s for something in-between.
panic = "abort"     # 遇到 panic 直接终止,不包含回溯信息
strip = true        # 去除符号表和调试信息

通过上述配置,我们的 exe 瘦身到 3144KB 👍,如果进一步设置opt-level="z"则可以做到2748KB

业务逻辑

终于到这一步了,我们需要真正实现工具的功能,计算通配符掩码可以直接在前端(用 JavaScript 实现),也可以使用后端 Rust 实现,考虑到计算量可能比较大(模糊掩码位较大时),这里选择 Rust 实现。

tauri 前后端交互

开始前,我们先做个简单的示例。开始前,我们需要确保已经安装了交互依赖pnpm i -D @tauri-apps/api

rust 复制代码
// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name:String)-> String {
    format!("Hi, {}, this's greet from Tauri!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
js 复制代码
// src/App.vue

import { invoke } from '@tauri-apps/api/core'

const greet = ()=>{
    invoke("greet", { name:"集成显卡"}).then(msg=>message.success("来自后端回复:"+msg))
}

掩码计算

rust 复制代码
/// 将 Ipv4Addr 转换为 32 位整数
fn ip_to_u32(ip: Ipv4Addr) -> u32 {
    let octets = ip.octets();
    ((octets[0] as u32) << 24) |
        ((octets[1] as u32) << 16) |
        ((octets[2] as u32) << 8) |
        (octets[3] as u32)
}

/// 将 32 位整数转换为 Ipv4Addr
fn u32_to_ip(n: u32) -> Ipv4Addr {
    Ipv4Addr::new(
        ((n >> 24) & 0xFF) as u8,
        ((n >> 16) & 0xFF) as u8,
        ((n >> 8) & 0xFF) as u8,
        (n & 0xFF) as u8,
    )
}

/// 计算与给定 IP 地址和通配符掩码匹配的所有 IP 地址
fn calculate_matching_ips(ip: Ipv4Addr, wildcard_mask: Ipv4Addr) -> Vec<Ipv4Addr> {
    // 将 IP 和通配符掩码转换为 32 位整数
    let ip_int = ip_to_u32(ip);
    let mask_int = ip_to_u32(wildcard_mask);

    // 计算固定位和可变位
    // 固定位:通配符掩码中为 0 的位,必须精确匹配
    // 可变位:通配符掩码中为 1 的位,可以是 0 或 1
    let fixed_bits = ip_int & !mask_int;
    let variable_mask = mask_int;

    // 计算可变位的数量
    let variable_bits_count = variable_mask.count_ones() as usize;

    // 检查可变位数量,避免生成过多 IP 地址导致性能问题
    if variable_bits_count > 20 {
        eprintln!("错误:通配符掩码包含太多可变位({}位),可能会生成超过一百万的 IP 地址,这可能导致性能问题。", variable_bits_count);
        std::process::exit(1);
    }

    // 计算可能的 IP 地址数量 (2^variable_bits_count)
    let total_ips = 1u32 << variable_bits_count;

    // 生成所有可能的 IP 地址
    let mut matching_ips = Vec::with_capacity(total_ips as usize);
    for i in 0..total_ips {
        // 将 i 的位填充到可变位的位置
        let mut ip = fixed_bits;
        let mut temp_i = i;

        // 遍历每个位,将 temp_i 的位填充到可变位
        for bit in 0..32 {
            if (variable_mask >> bit) & 1 != 0 {
                ip |= (temp_i & 1) << bit;
                temp_i >>= 1;
            }
        }

        matching_ips.push(u32_to_ip(ip));
    }

    matching_ips
}

#[tauri::command]
fn calculate(ip:String, mask:String)->Result<Vec<Ipv4Addr>, String> {
    // 解析 IP 地址
    let ip = match Ipv4Addr::from_str(&ip) {
        Ok(ip)  => ip,
        Err(_)  => return Err(format!("无效的 IP 地址: {}", &ip)),
    };

    // 解析通配符掩码
    let wildcard_mask = match Ipv4Addr::from_str(&mask) {
        Ok(m)   => m,
        Err(_)  => return Err(format!("无效的通配符掩码: {}", &mask)),
    };

    // 计算所有匹配的 IP 地址
    let matching_ips = calculate_matching_ips(ip, wildcard_mask);
    Ok(matching_ips)
}

成果展示

相关推荐
Ray662 分钟前
AviatorScript 表达式引擎
后端
回家路上绕了弯1 小时前
深度理解 Lock 与 ReentrantLock:Java 并发编程的高级锁机制
java·后端
Captaincc1 小时前
TRAE 首场 Meetup:8月16日,期待与你在北京相聚
前端·后端·trae
肩塔didi2 小时前
用 Pixi 管理 Python 项目:打通Conda 和 PyPI 的边界
后端·python·github
dylan_QAQ2 小时前
【附录】相对于BeanFactory ,ApplicationContext 做了哪些企业化的增强?
后端·spring
唐诗2 小时前
VMware Mac m系列安装 Windws 11,保姆级教程
前端·后端·github
Lx3523 小时前
Hadoop新手必知的10个高效操作技巧
hadoop·后端
写bug写bug3 小时前
搞懂Spring任务执行器和调度器模型
java·后端·spring
二闹3 小时前
TCP三次握手的智慧:为什么不是两次或四次?
后端·tcp/ip
熊猫片沃子3 小时前
Maven在使用过程中的核心知识点总结
java·后端·maven