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)
}

成果展示

相关推荐
做运维的阿瑞4 小时前
Python零基础入门:30分钟掌握核心语法与实战应用
开发语言·后端·python·算法·系统架构
猿究院-陆昱泽5 小时前
Redis 五大核心数据结构知识点梳理
redis·后端·中间件
yuriy.wang5 小时前
Spring IOC源码篇五 核心方法obtainFreshBeanFactory.doLoadBeanDefinitions
java·后端·spring
咖啡教室7 小时前
程序员应该掌握的网络命令telnet、ping和curl
运维·后端
你的人类朋友8 小时前
Let‘s Encrypt 免费获取 SSL、TLS 证书的原理
后端
老葱头蒸鸡8 小时前
(14)ASP.NET Core2.2 中的日志记录
后端·asp.net
李昊哲小课9 小时前
Spring Boot 基础教程
java·大数据·spring boot·后端
码事漫谈9 小时前
C++内存越界的幽灵:为什么代码运行正常,free时却崩溃了?
后端
Swift社区9 小时前
Spring Boot 3.x + Security + OpenFeign:如何避免内部服务调用被重复拦截?
java·spring boot·后端
90后的晨仔9 小时前
Mac 上配置多个 Gitee 账号的完整教程
前端·后端