C# SFTP客户端实现文件传输

一、SFTP信息

SSH:加密的远程登陆/命令通道协议,可以链接到另一台机器执行命令,也可以传输一些文件;

SFTP:跑在SSH上面的文件传输协议,端口为22;

FTP:1971年的明文传输协议,端口21,与SFTP没有关系;

二、服务端OpenSSH

  1. systemctl enable --now ssh:开机自启 + 立即启动,启动的服务名为ssh.service

  2. /etc/init.d/sshd start:运行sshd脚本,start为参数,还有stop啥的

  3. ssh-keygen -A:生成密钥ssh_host_*_key,在服务端与客户端进行连接时,客户端发给服务端由它生成的公钥,服务端会留存这个公钥以识别OpenSSH

  4. /etc/ssh/sshd_config为SSH的配置文件,里面有端口、登陆账号(即Linux账号)、密钥等;

三、客户端

  1. 加载远程文件\目录列表

    SftpClient client = new SftpClient(ip, port, userName, password); // 除了port为int外,其他参数都是string,用户名和密码是Linux允许访问OpenSSH账号名和密码,sshd_config AllowUser那里做配置的
    client.Connect();
    List entries = client.ListDirectory(path) // path服务端的路径
    .Where(t => t.Name != "." && t.Name != ".." && !t.Name.StartsWith('.')) // 剔除"."当前目录、".."上级目录、"."开头的配置文件
    .OrderByDescending(t => t.IsDirectory)
    .ThenBy(t => t.Name, StringComparer.OrdinalIgnoreCase)
    .Select(t => mew TransFileInfo()
    {
    Name = t.Name,
    Size = t.IsDirectory ? 0 : t.Length,
    Time = t.LastWriteTime.ToString("yyyy-MM-dd HH:mm"),
    IsDirectory = t.IsDirectory,
    FullPath = t.Name
    });

  2. 删除远程文件\目录

    SftpClient client = new SftpClient(ip, port, userName, password);
    client.Connect(); // SftpClient的创建和连接的成本很低,可以在每次执行时都创建并连接
    SftpFileAttributes attrs = client.GetAttributes(deleteFilePath);
    if (attrs.IsDirectory)
    // 写个递归删除所有文件夹中的文件就行
    else
    client.DeleteFile(deleteFilePath);

  3. 重命名

    client.RenameFile(oldFilePath, newPath);

  4. 创建文件夹

    client.CreateDirectory(dirPath);

  5. 下载

    long totalLength = client.GetAttributes(remotePath).Size; // 获取一下远程文件的大小
    string tempPath = localPath + ".tmp"; // 先将远程文件保存至临时文件,结束后再删除掉.tmp后缀
    long offset = 0;
    if (File.Exits(tempPath))
    {
    offset = (new FileInfo(tempPath)).Length; // 因为提供的断点续传功能,所以临时文件的长度作为偏置,续传时就从这里开始
    if (offset >= totalLength)
    offset = totalLength; // 偏置为totalLength就算是下载完毕了
    }

    using StfpFileStream remoteStream = client.OpenRead(remotePath);
    remoteStream.Seek(offset, SeekOrigin.Begin); // SeekOrigin.Begin从文件开头开始偏移

    using FileStream localStream = new FileStream(tempPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 256 * 1024); // 每次只读取256kb
    localStream.Seek(offset, SeekOrigin.Begin);

    byte[] buffer = new byte[256 * 1024];
    while(true)
    {
    if(!await Check())
    continue;

    复制代码
     int read = await remoteStream.ReadAsync(buffer.AsMemory(0, buffer.Length));  // Memory类似(指针 + 长度),由GC回收
     if (read == 0)
         break;
    
     await localStream.WriteAsync(buffer.AsMemory(0, read));

    }

    // 回收流资源 + 复制临时文件
    localStream.Dispose();
    if (File.Exits(localPath))
    File.Delete(localPath);

    File.Move(tempPath, localPath);

    async void Check()
    {
    TaskCompletionSource? tcs;
    lock(_lock)
    {
    if (isPaused)
    return false; // 这里省略了,isPaused为true时,while(true)那边不继续

    复制代码
         tcs = _pauseTcs;
     }
    
     if (tcs != null)
         await tcs.Task.ConfigureAwait(false);  // 在这里等

    }

    void Resume()
    {
    TaskCompletionSource? tcs;
    lock(_lock)
    {
    if (!isPaused)
    return;

    复制代码
         isPaused = false;
         tcs = _pauseTcs;
         _pauseTcs = null;
     }
    
     tcs?.TrySetResult(true);    

    }

    void Cancel()
    {
    TaskCompletionSource? tcs;
    lock(_lock)
    {
    if (isPaused)
    return;

    复制代码
         isPaused = true;
         _pauseTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationAsynchronously);
     }
    
     tcs?.TrySetResult(false);    

    }

相关推荐
samble1 小时前
从零搭建灌装监控系统(四):深色主题与样式系统,ResourceDictionary 统一管理
c#·wpf·mvvm·样式·工业控制
cjp5602 小时前
024 . WPF MVVM类封装优化
c#
淡海水13 小时前
13-04-面试-源码级深度追问链
数据结构·unity·面试·c#·游戏引擎·源码·il2cpp
asdzx6714 小时前
如何使用 C# 提取 PPT 文本与图片
c#·ppt·提取文本
2501_9307077820 小时前
使用C#代码使用条件格式为 Excel 隔行设置颜色
c#·excel
何以解忧唯有撸码1 天前
【开源】c#造个轮子-日程安排工具
c#·自定义控件
周杰伦fans1 天前
CAD菜单栏和工具栏都不见了,怎么显示?
其他·c#
曹牧1 天前
C#:HashSet<T> 去重
c#
czhc11400756631 天前
起点、口径与因果:今天用错的七把尺子
c#