Rust开发SSH客户端方案推荐

学习笔记作者:admin日期:2025-10-07点击:19

摘要:本文总结了使用Rust语言结合russh库开发跨平台SSH客户端的推荐方案,包括UI框架选择、技术栈建议和示例代码。

项目目标总结

| 需求 | 要求 | |------|------| | 开发语言 | Rust | | SSH 库 | `russh`(纯 Rust,异步,安全) | | 跨平台 | macOS / Windows / Linux | | UI 界面 | 美观、现代、响应式 | | 开发效率 | 尽量“低代码”、使用流行框架 | | 功能 | 连接管理、终端显示、SFTP(可选) |

推荐整体技术栈

| 模块 | 推荐方案 | 理由 | |------|---------|------| | **SSH 核心** | [`russh`](https://github.com/warp-rs/russh) | 纯 Rust,异步,安全,支持密钥、密码、跳板机等 | | **UI 框架** | [`tao` + `wry` + `slint`] 或 [`eframe` (egui)] | 跨平台桌面 UI,Rust 原生,低代码友好 | | **终端渲染** | 自研或集成 `xterm.js`(Web) / `copypasta`(本地) | 显示 SSH 终端输出 | | **配置管理** | `serde` + `serde_json` + `directories` | 存储连接配置 | | **异步运行时** | `tokio` | `russh` 依赖异步运行时 |

推荐架构方案(推荐使用 eframe + egui)

### ✅ 方案一:`eframe` + `egui`(推荐!低代码、跨平台、快速开发) > **最适合“低代码 + 快速原型 + 美观 UI”需求** #### ? 技术栈 - `eframe`: egui 的桌面应用框架(支持 Web 和 Native) - `egui`: 声明式 UI 框架,类似 React,Rust 原生 - `russh`: SSH 客户端 - `tokio`: 异步运行时 - `tui`: 可选,用于本地终端模拟(或直接用 `egui` 绘制终端) - `serde` / `config_dir`: 配置持久化 #### ✅ 优点 - UI 开发极快,**接近“低代码”体验** - 跨平台完美支持(Win/macOS/Linux) - 主题可定制,支持深色模式 - 社区活跃,有大量示例 - 可打包为独立二进制(无浏览器依赖) #### ? 示例项目结构 ```bash ssh-client/ ├── src/ │ ├── main.rs # eframe 入口 │ ├── app.rs # 主应用逻辑 │ ├── ssh_session.rs # russh 连接管理 │ └── config.rs # 连接配置存储 ├── assets/ # 图标、配置文件 ├── Cargo.toml ``` #### ?️ UI 设计建议 - 左侧:连接列表(树状或列表) - 中部:终端显示区域(用 `egui::TextEdit::multiline` 模拟终端) - 顶部:连接按钮、保存配置 - 支持多标签页(可选 `egui_extras::Strip` 或自定义 tab)

推荐选择:`eframe + egui + russh`

快速开始示例(eframe + russh)

### 1. `Cargo.toml`
[package]
name = "rusty-ssh-client"
version = "0.1.0"
edition = "2021"

[dependencies]
eframe = "0.24"
egui = "0.24"
russh = "0.34"
russh-clipboard = "0.3"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
directories = "5.0"
futures-util = "0.3"
### 2. `main.rs`
use eframe::egui;
use std::sync::{Arc, Mutex};
use tokio::runtime::Runtime;

mod ssh_session;

#[derive(Default)]
struct SshClientApp {
    address: String,
    username: String,
    password: String,
    output: String,
    is_connected: bool,
    session: Option>>>,
    rt: Option,
}

impl eframe::App for SshClientApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.heading("Rusty SSH Client");

            ui.horizontal(|ui| {
                ui.label("Address:");
                ui.text_edit_singleline(&mut self.address);
            });
            ui.horizontal(|ui| {
                ui.label("Username:");
                ui.text_edit_singleline(&mut self.username);
            });
            ui.horizontal(|ui| {
                ui.label("Password:");
                ui.text_edit_singleline(&mut self.password);
            });

            if ui.button("Connect").clicked() && !self.is_connected {
                if let Ok(rt) = tokio::runtime::Runtime::new() {
                    let session = Arc::new(Mutex::new(None));
                    let session_clone = session.clone();

                    rt.spawn(async move {
                        if let Ok(mut ssh) = ssh_session::connect(&self.address, &self.username, &self.password).await {
                            if let Ok(output) = ssh.execute_command("uname -a").await {
                                let mut guard = session_clone.lock().unwrap();
                                *guard = Some(ssh);
                                // 这里可以发消息回 UI(建议用 channel)
                            }
                        }
                    });

                    self.rt = Some(rt);
                    self.session = Some(session);
                    self.is_connected = true;
                }
            }

            ui.add(egui::TextEdit::multiline(&mut self.output).desired_rows(10));
        });
    }
}

fn main() -> Result<(), eframe::Error> {
    env_logger::init();
    let options = eframe::NativeOptions::default();
    eframe::run_native(
        "SSH Client",
        options,
        Box::new(|_cc| Box::new(SshClientApp::default())),
    )
}
### 3. `ssh_session.rs`(简化版)
use russh::{self, Client, Config};
use russh::client::{Msg, Session};
use std::net::TcpStream;
use std::sync::Arc;
use tokio::net::TcpStream as TokioTcp;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

pub struct SshSession {
    pub session: Session,
}

pub async fn connect(
    addr: &str,
    user: &str,
    password: &str,
) -> Result> {
    let config = Arc::new(Config::default());
    let mut session = Session::connect(config, addr, tokio::io::stdout()).await?;
    session.authenticate_password(user, password).await?;
    Ok(SshSession { session })
}

impl SshSession {
    pub async fn execute_command(&mut self, cmd: &str) -> Result> {
        let mut channel = self.session.channel_open_session().await?;
        channel.exec(true, cmd).await?;
        let mut output = String::new();
        channel.read_to_string(&mut output).await?;
        Ok(output)
    }
}

打包发布

使用 `cargo bundle` 或 `tauri` 打包为: - macOS: `.app` - Windows: `.exe` - Linux: `.AppImage` / `.deb`

进阶功能建议

| 功能 | 实现方式 | |------|----------| | 保存连接 | `serde_json` 存到 `~/.config/your-app/config.json` | | 多标签 | `egui::containers::Tabs` | | SFTP 文件管理 | 使用 `russh` 的 SFTP 模块 | | 主题切换 | `eframe` 支持深色/浅色模式 | | 快捷命令 | 自定义按钮执行常用命令 |

总结:推荐方案

| 项目 | 推荐选择 | |------|----------| | **UI 框架** | `eframe + egui`(首选)或 `Tauri + React`(UI 更强) | | **SSH 库** | `russh` | | **运行时** | `tokio` | | **配置** | `serde` + `directories` | | **终端渲染** | `egui::TextEdit` 或自定义字符网格 |

上一篇      下一篇