一、SFTP信息
SSH:加密的远程登陆/命令通道协议,可以链接到另一台机器执行命令,也可以传输一些文件;
SFTP:跑在SSH上面的文件传输协议,端口为22;
FTP:1971年的明文传输协议,端口21,与SFTP没有关系;
二、服务端OpenSSH
-
systemctl enable --now ssh:开机自启 + 立即启动,启动的服务名为ssh.service
-
/etc/init.d/sshd start:运行sshd脚本,start为参数,还有stop啥的
-
ssh-keygen -A:生成密钥ssh_host_*_key,在服务端与客户端进行连接时,客户端发给服务端由它生成的公钥,服务端会留存这个公钥以识别OpenSSH
-
/etc/ssh/sshd_config为SSH的配置文件,里面有端口、登陆账号(即Linux账号)、密钥等;
三、客户端
-
加载远程文件\目录列表
SftpClient client = new SftpClient(ip, port, userName, password); // 除了port为int外,其他参数都是string,用户名和密码是Linux允许访问OpenSSH账号名和密码,sshd_config AllowUser那里做配置的
client.Connect();
Listentries = 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
}); -
删除远程文件\目录
SftpClient client = new SftpClient(ip, port, userName, password);
client.Connect(); // SftpClient的创建和连接的成本很低,可以在每次执行时都创建并连接
SftpFileAttributes attrs = client.GetAttributes(deleteFilePath);
if (attrs.IsDirectory)
// 写个递归删除所有文件夹中的文件就行
else
client.DeleteFile(deleteFilePath); -
重命名
client.RenameFile(oldFilePath, newPath);
-
创建文件夹
client.CreateDirectory(dirPath);
-
下载
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);}