|
前言
有个项目,需要在前端有个管理终端可以 SSH 到主控机的终端,如果不考虑用户使用 vim 等需要在控制台内现实界面的软件的话,其实使用 Process 类型去启动相应程序就够了。而这次的需求则需要考虑用户会做相关设置。
原理
这里用到的原理是伪终端。伪终端(pseudo terminal)是现代操作系统的一个功能,他会模拟一对输入输出设备来模拟终端环境去执行相应的进程。伪终端通常会给相应的进程提供例如环境变量或文件等来告知他在终端中运行,这样像 vim 这样的程序可以在最后一行输出命令菜单或者像 npm / pip 这样的程序可以打印炫酷的进度条。通常在我们直接创建子进程的时候,在 Linux 上系统自带了 openpty 方法可以打开伪终端,而在 Windows 上则等到 Windows Terminal 推出后才出现了真正的系统级伪终端。下面付一张来自微软博客的伪终端原理图,Linux 上的原理与之类似。
基本设计
建立连接与监听终端输出
监听前端输入
graph TD;<template>
</template>A[终端窗口收到键盘事件] --> B[SignalR 发送请求];<template>
</template>B --> C[后台转发到对应终端]超时与关闭
graph TD;<template>
</template>A[当 SignalR 发送断开连接或终端超时] --> B[关闭终端进程];依赖库
portable_pty
这里用到这个 Rust 库来建立终端,这个库是一个独立的进程,每次建立连接都会运行。这里当初考虑过直接在 ASP.NET Core 应用里调用 vs-pty(微软开发的,用在 vs 里的库,可以直接在 vs 安装位置复制一份),但是 vs-pty 因为种种原因在 .NET 7 + Ubuntu 22.04 的环境下运行不起来故放弃了。
xterm.js
这个是前端展示终端界面用的库,据说 vs code 也在用这个库,虽然文档不多,但是用起来真的很简单。
SignalR
这个不多说了,咱 .NET 系列 Web 实时通信选他就没错。
代码
废话不多讲了,咱还是直接看代码吧,这里代码还是比较长的,我节选了一些必要的代码。具体 SignalR 之类的配置,还请读者自行参考微软官方文档。
- main.rs 这个 Rust 代码用于建立伪终端并和 .NET 服务通信,这里使用了最简单的 UDP 方式通信。
- use portable_pty::{self, native_pty_system, CommandBuilder, PtySize};
- use std::{io::prelude::*, sync::Arc};
- use tokio::net::UdpSocket;
- #[tokio::main(flavor = "multi_thread", worker_threads = 4)]
- async fn main() -> Result<(), Box<dyn std::error::Error>> {
- <template>
-
- </template><template>
-
- </template>let args = std::env::args().collect::<Vec<_>>();
- <template>
-
- </template><template>
-
- </template>// 启动一个终端
- <template>
-
- </template><template>
-
- </template>let pty_pair = native_pty_system().openpty(PtySize {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>rows: args.get(2).ok_or("NoNumber")?.parse()?,
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>cols: args.get(3).ok_or("NoNumber")?.parse()?,
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>pixel_width: 0,
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>pixel_height: 0,
- <template>
-
- </template><template>
-
- </template>})?;
- <template>
-
- </template><template>
-
- </template>// 执行传进来的命令
- <template>
-
- </template><template>
-
- </template>let mut cmd = CommandBuilder::new(args.get(4).unwrap_or(&"bash".to_string()));
- <template>
-
- </template><template>
-
- </template>if args.len() > 5 {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>cmd.args(&args[5..]);
- <template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template>let mut proc = pty_pair.slave.spawn_command(cmd)?;
- <template>
-
- </template><template>
-
- </template>// 绑定输入输出
- <template>
-
- </template><template>
-
- </template>let mut reader = pty_pair.master.try_clone_reader()?;
- <template>
-
- </template><template>
-
- </template>let mut writer = pty_pair.master.take_writer()?;
- <template>
-
- </template><template>
-
- </template>// 绑定网络
- <template>
-
- </template><template>
-
- </template>let main_socket = Arc::new(UdpSocket::bind("localhost:0").await?);
- <template>
-
- </template><template>
-
- </template>let recv_socket = main_socket.clone();
- <template>
-
- </template><template>
-
- </template>let send_socket = main_socket.clone();
- <template>
-
- </template><template>
-
- </template>let resize_socket = UdpSocket::bind("localhost:0").await?;
- <template>
-
- </template><template>
-
- </template>// 连接到主服务后发送地址
- <template>
-
- </template><template>
-
- </template>main_socket
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>.connect(args.get(1).ok_or("NoSuchAddr")?)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>.await?;
- <template>
-
- </template><template>
-
- </template>main_socket
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>.send(&serde_json::to_vec(&ClientAddr {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>main: main_socket.local_addr()?.to_string(),
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>resize: resize_socket.local_addr()?.to_string(),
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>})?)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>.await?;
- <template>
-
- </template><template>
-
- </template>// 读取终端数据并发送
- <template>
-
- </template><template>
-
- </template>let read = tokio::spawn(async move {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>loop {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>let mut buf = [0; 1024];
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>let n = reader.read(&mut buf).unwrap();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if n == 0 {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>continue;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>println!("{:?}", &buf[..n]);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>send_socket.send(&buf[..n]).await.unwrap();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template>});
- <template>
-
- </template><template>
-
- </template>// 接收数据并写入终端
- <template>
-
- </template><template>
-
- </template>let write = tokio::spawn(async move {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>loop {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>let mut buf = [0; 1024];
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>let n = recv_socket.recv(&mut buf).await.unwrap();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if n == 0 {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>continue;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>println!("{:?}", &buf[..n]);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>writer.write_all(&buf[..n]).unwrap();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template>});
- <template>
-
- </template><template>
-
- </template>// 接收调整窗口大小的数据
- <template>
-
- </template><template>
-
- </template>let resize = tokio::spawn(async move {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>let mut buf = [0; 1024];
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>loop {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>let n = resize_socket.recv(&mut buf).await.unwrap();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if n == 0 {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>continue;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>let size: WinSize = serde_json::from_slice(buf[..n].as_ref()).unwrap();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>pty_pair
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>.master
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>.resize(PtySize {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>rows: size.rows,
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>cols: size.cols,
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>pixel_width: 0,
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>pixel_height: 0,
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>})
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>.unwrap();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template>});
- <template>
-
- </template><template>
-
- </template>// 等待进程结束
- <template>
-
- </template><template>
-
- </template>let result = proc.wait()?;
- <template>
-
- </template><template>
-
- </template>write.abort();
- <template>
-
- </template><template>
-
- </template>read.abort();
- <template>
-
- </template><template>
-
- </template>resize.abort();
- <template>
-
- </template><template>
-
- </template>if 0 == result.exit_code() {
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>std::process::exit(result.exit_code() as i32);
- <template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template>return Ok(());
- }
- /// 窗口大小
- #[derive(serde::Deserialize)]
- struct WinSize {
- <template>
-
- </template><template>
-
- </template>/// 行数
- <template>
-
- </template><template>
-
- </template>rows: u16,
- <template>
-
- </template><template>
-
- </template>/// 列数
- <template>
-
- </template><template>
-
- </template>cols: u16,
- }
- /// 客户端地址
- #[derive(serde::Serialize)]
- struct ClientAddr {
- <template>
-
- </template><template>
-
- </template>/// 主要地址
- <template>
-
- </template><template>
-
- </template>main: String,
- <template>
-
- </template><template>
-
- </template>/// 调整窗口大小地址
- <template>
-
- </template><template>
-
- </template>resize: String,
- }
复制代码
- SshPtyConnection.cs 这个代码用于维持一个后台运行的 Rust 进程,并管理他的双向通信。
- <template>
-
- </template><template>
-
- </template>public class SshPtyConnection : IDisposable
- <template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 客户端地址
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private class ClientEndPoint
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public required string Main { get; set; }
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public required string Resize { get; set; }
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 窗口大小
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private class WinSize
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public int Cols { get; set; }
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public int Rows { get; set; }
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// SignalR 上下文
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private readonly IHubContext<SshHub> _hubContext;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 日志记录器
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private readonly ILogger<SshPtyConnection> _logger;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// UDP 客户端
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private readonly UdpClient udpClient;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 最后活动时间
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private DateTime lastActivity = DateTime.UtcNow;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 是否已释放
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private bool disposedValue;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 是否已释放
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public bool IsDisposed => disposedValue;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 最后活动时间
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public DateTime LastActivity => lastActivity;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 取消令牌
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 窗口大小
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public event EventHandler<EventArgs> Closed = delegate { };
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 构造函数
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="hubContext"></param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="logger"></param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <exception cref="ArgumentNullException"></exception>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public SshPtyConnection(IHubContext<SshHub> hubContext, ILogger<SshPtyConnection> logger)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger = logger ?? throw new ArgumentNullException(nameof(logger));
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>lastActivity = DateTime.Now;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>udpClient = new(IPEndPoint.Parse("127.0.0.1:0"));
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 开始监听
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="connectionId">连接 ID</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="username">用户名</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="height">行数</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="width">列数</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public async void StartAsync(string connectionId, string username, int height, int width)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var token = CancellationTokenSource.Token;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogInformation("process starting");
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>// 启动进程
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>using var process = Process.Start(new ProcessStartInfo
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>FileName = OperatingSystem.IsOSPlatform("windows") ? "PtyWrapper.exe" : "pty-wrapper",
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>// 这里用了 su -l username,因为程序直接部署在主控机的 root 下,所以不需要 ssh 只需要切换用户即可,如果程序部署在其他机器上,需要使用 ssh
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>ArgumentList = { udpClient.Client.LocalEndPoint!.ToString() ?? "127.0.0.1:0", height.ToString(), width.ToString(), "su", "-l", username }
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>});
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>// 接收客户端地址
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var result = await udpClient.ReceiveAsync();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var clientEndPoint = await JsonSerializer.DeserializeAsync<ClientEndPoint>(new MemoryStream(result.Buffer), new JsonSerializerOptions
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>PropertyNameCaseInsensitive = true
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>});
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (clientEndPoint == null)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>CancellationTokenSource.Cancel();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>process!.Exited += (_, _) => CancellationTokenSource.Cancel();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var remoteEndPoint = IPEndPoint.Parse(clientEndPoint.Main);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>udpClient.Connect(remoteEndPoint);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var stringBuilder = new StringBuilder();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>// 接收客户端数据,并发送到 SignalR,直到客户端断开连接或者超时 10 分钟
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>while (!token.IsCancellationRequested && lastActivity.AddMinutes(10) > DateTime.Now && !(process?.HasExited ?? false))
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>try
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>lastActivity = DateTime.Now;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var buffer = await udpClient.ReceiveAsync(token);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>await _hubContext.Clients.Client(connectionId).SendAsync("WriteDataAsync", Encoding.UTF8.GetString(buffer.Buffer));
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>stringBuilder.Clear();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>catch (Exception e)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogError(e, "ConnectionId: {ConnectionId} Unable to read data and send message.", connectionId);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>break;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>// 如果客户端断开连接或者超时 10 分钟,关闭进程
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (process?.HasExited ?? false) process?.Kill();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (lastActivity.AddMinutes(10) < DateTime.Now)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogInformation("ConnectionId: {ConnectionId} Pty session has been closed because of inactivity.", connectionId);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>try
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>await _hubContext.Clients.Client(connectionId).SendAsync("WriteErrorAsync", "InactiveTimeTooLong");
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>catch (Exception e)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogError(e, "ConnectionId: {ConnectionId} Unable to send message.", connectionId);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (token.IsCancellationRequested)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogInformation("ConnectionId: {ConnectionId} Pty session has been closed because of session closed.", connectionId);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>try
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>await _hubContext.Clients.Client(connectionId).SendAsync("WriteErrorAsync", "SessionClosed");
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>catch (Exception e)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogError(e, "ConnectionId: {ConnectionId} Unable to send message.", connectionId);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>Dispose();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 接收 SignalR 数据,并发送到客户端
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="data">数据</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <returns></returns>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <exception cref="AppException"></exception>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public async Task WriteDataAsync(string data)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (disposedValue)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>throw new AppException("SessionClosed");
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>try
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>lastActivity = DateTime.Now;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>await udpClient.SendAsync(Encoding.UTF8.GetBytes(data));
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>catch (Exception e)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>CancellationTokenSource.Cancel();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>Dispose();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>throw new AppException("SessionClosed", e);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 回收资源
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="disposing"></param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>protected virtual void Dispose(bool disposing)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (!disposedValue)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (disposing)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>CancellationTokenSource.Cancel();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>udpClient.Dispose();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>disposedValue = true;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>Closed(this, new EventArgs());
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public void Dispose()
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>Dispose(disposing: true);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>GC.SuppressFinalize(this);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template>}
复制代码
- SshService 这段代码用于管理 SshPtyConnection 和 SignalR 客户端连接之间的关系
- <template>
-
- </template><template>
-
- </template>public class SshService : IDisposable
- <template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private bool disposedValue;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private readonly IHubContext<SshHub> _hubContext;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private readonly ILoggerFactory _loggerFactory;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private Dictionary<string, SshPtyConnection> _connections;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public SshService(IHubContext<SshHub> hubContext, ILoggerFactory loggerFactory)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_connections = new Dictionary<string, SshPtyConnection>();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 创建终端连接
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="connectionId">连接 ID</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="username">用户名</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="height">行数</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="width">列数</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <returns></returns>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <exception cref="InvalidOperationException"></exception>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public Task CreateConnectionAsync(string connectionId, string username, int height, int width)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (_connections.ContainsKey(connectionId))
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>throw new InvalidOperationException();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var connection = new SshPtyConnection(_hubContext, _loggerFactory.CreateLogger<SshPtyConnection>());
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>connection.Closed += (sender, args) =>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_hubContext.Clients.Client(connectionId).SendAsync("WriteErrorAsync", "SessionClosed");
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_connections.Remove(connectionId);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>};
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_connections.Add(connectionId, connection);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>// 运行一个后台线程
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>connection.StartAsync(connectionId, username, height, width);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return Task.CompletedTask;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 写入数据
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="connectionId">连接 ID</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="data">数据</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <exception cref="AppException"></exception>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public async Task ReadDataAsync(string connectionId, string data)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (_connections.TryGetValue(connectionId, out var connection))
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>await connection.WriteDataAsync(data);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>else
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>throw new AppException("SessionClosed");
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 关闭连接
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="connectionId">连接 ID</param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <exception cref="AppException"></exception>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public Task CloseConnectionAsync(string connectionId)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (_connections.TryGetValue(connectionId, out var connection))
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>connection.Dispose();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>else
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>throw new AppException("SessionClosed");
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return Task.CompletedTask;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// 回收资源
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// </summary>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>/// <param name="disposing"></param>
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>protected virtual void Dispose(bool disposing)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (!disposedValue)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (disposing)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>foreach (var item in _connections.Values)
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>item.Dispose();
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>disposedValue = true;
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public void Dispose()
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>Dispose(disposing: true);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>GC.SuppressFinalize(this);
- <template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}
- <template>
-
- </template><template>
-
- </template>}
复制代码
- WebSsh.vue 这段代码是使用 vue 展示终端窗口的代码
- SshHub.cs 这个文件是 SignalR 的 Hub 文件,用来做监听的。
- <template>
-
- </template><template>
-
- </template>[Authorize]<template>
-
- </template><template>
-
- </template>public class SshHub : Hub<template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private readonly SshService _sshService;<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>private readonly ILogger _logger;<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>public SshHub(SshService sshService, ILogger logger)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_sshService = sshService ?? throw new ArgumentNullException(nameof(sshService));<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger = logger ?? throw new ArgumentNullException(nameof(logger));<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> /// 创建一个新的终端<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> public async Task CreateNewTerminalAsync(int height = 24, int width = 80)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>try<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>var username = Context.User?.FindFirst("preferred_username")?.Value;<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (username == null)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return new BaseResponse<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>Code = 401,<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>Message = "NoUsername"<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>};<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>if (!Context.User?.IsInRole("user") ?? false)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>username = "root";<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogInformation($"{username}");<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>await _sshService.CreateConnectionAsync(Context.ConnectionId, username, height, width);<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return new BaseResponse();<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>catch (InvalidOperationException)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return new BaseResponse() { Code = 500, Message = "TerminalAlreadyExist" };<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>catch (Exception e)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogError(e, "ConnectionId: {ConnectionId} No such pty session.", Context.ConnectionId);<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return new BaseResponse() { Code = 500, Message = "UnableToCreateTerminal" };<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> /// 读取输入数据<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> public async Task ReadDataAsync(string data)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>try<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>await _sshService.ReadDataAsync(Context.ConnectionId, data);<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return new BaseResponse();<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>catch (Exception e)<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>_logger.LogError(e, "ConnectionId: {ConnectionId} No such pty session.", Context.ConnectionId);<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>return new BaseResponse { Message = "NoSuchSeesion", Code = 400 };<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template>}<template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template> /// 客户端接口<template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template> public interface ISshHubClient<template>
-
- </template><template>
-
- </template>{<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> /// 写入输出数据<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> Task WriteDataAsync(string data);<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> /// 写入错误数据<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template>///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> ///<template>
-
- </template><template>
-
- </template><template>
-
- </template><template>
-
- </template> Task WriteErrorAsync(string data);<template>
-
- </template><template>
-
- </template>}
复制代码 参考文献
来源:https://www.cnblogs.com/aobaxu/archive/2023/10/31/17799346.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|