C#使用SFTP批量上传和下载一个目录下的所有文件
实现C#.NET 8使用SFTP批量上传和下载一个目录下的所有文件的程序,要求参数所有参数写到ini配置文件中,多个文件同时操作时实现异步多线程,异常处理和详细的日志信息输出,需要考虑线程安全和程序的安全性。
在C#.NET 8中,你可以使用SSH.NET库来通过SFTP(SSH File Transfer Protocol)上传和下载文件。SSH.NET是一个流行的开源库,用于在.NET应用程序中实现SSH和SFTP功能。
使用SSH.NET库在C#.NET 8中上传和下载文件的步骤:
- 安装SSH.NET库:你可以通过NuGet包管理器来安装SSH.NET库。在你的项目中打开“包管理器控制台”并运行以下命令:
【shell】
Install-Package SSH.NET
-
编写代码:
• 上传文件:
using Renci.SshNet;
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
string host = "your.sftp.server";
string username = "your_username";
string password = "your_password";
string localFilePath = @"C:\path\to\local\file.txt";
string remoteFilePath = "/path/to/remote/file.txt";
using (var client = new SftpClient(host, username, password))
{
client.Connect();
using (var fileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read))
{
client.UploadFile(fileStream, remoteFilePath);
}
client.Disconnect();
}
Console.WriteLine("File uploaded successfully.");
}
}
• 下载文件:
【csharp】
using Renci.SshNet;
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
string host = "your.sftp.server";
string username = "your_username";
string password = "your_password";
string remoteFilePath = "/path/to/remote/file.txt";
string localFilePath = @"C:\path\to\local\downloaded_file.txt";
using (var client = new SftpClient(host, username, password))
{
client.Connect();
using (var fileStream = new FileStream(localFilePath, FileMode.Create, FileAccess.Write))
{
client.DownloadFile(remoteFilePath, fileStream);
}
client.Disconnect();
}
Console.WriteLine("File downloaded successfully.");
}
}
-
注意事项:
• 确保你提供的SFTP服务器地址、用户名和密码是正确的。
• 确保本地和远程文件路径是有效的,并且应用程序有适当的读写权限。
• 你可以根据需要添加错误处理逻辑,例如处理连接失败、文件不存在等情况。
通过这些步骤,你就可以在C#.NET 8中使用SSH.NET库通过SFTP上传和下载文件了。
以下是一个基于C#.NET 8实现的SFTP批量文件传输程序,满足您提出的所有需求。代码包含配置文件解析、异步多线程处理、异常记录和安全措施。
using Microsoft.Extensions.Configuration;
using Renci.SshNet;
using System.Collections.Concurrent;
using System.Diagnostics;
var config = new ConfigurationBuilder()
.AddIniFile("config.ini")
.Build();
var sftpConfig = config.GetSection("SFTP");
var loggingConfig = config.GetSection("Logging");
// 初始化日志系统
var logger = new Logger(loggingConfig["LogPath"] ?? "sftp_transfer.log");
try
{
var maxThreads = int.Parse(sftpConfig["MaxThreads"] ?? "4");
var operationMode = sftpConfig["Mode"] ?? "Upload";
var localPath = sftpConfig["LocalDirectory"];
var remotePath = sftpConfig["RemoteDirectory"];
// 验证配置参数
if (string.IsNullOrEmpty(localPath) || string.IsNullOrEmpty(remotePath))
{
logger.LogError("Invalid directory configuration");
return;
}
// 创建连接工厂
var connectionFactory = new SftpConnectionFactory(
sftpConfig["Host"],
int.Parse(sftpConfig["Port"] ?? "22"),
sftpConfig["Username"],
sftpConfig["Password"],
sftpConfig["PrivateKeyPath"]
);
var processor = new SftpBatchProcessor(
connectionFactory,
logger,
maxThreads
);
var sw = Stopwatch.StartNew();
if (operationMode.Equals("Upload", StringComparison.OrdinalIgnoreCase))
{
await processor.UploadDirectoryAsync(localPath, remotePath);
}
else
{
await processor.DownloadDirectoryAsync(remotePath, localPath);
}
sw.Stop();
logger.LogInfo($"Operation completed in {sw.Elapsed.TotalSeconds:0.00} seconds");
}
catch (Exception ex)
{
logger.LogError($"Critical error: {ex}");
}
public class SftpBatchProcessor
{
private readonly SftpConnectionFactory _connectionFactory;
private readonly Logger _logger;
private readonly int _maxDegreeOfParallelism;
public SftpBatchProcessor(
SftpConnectionFactory connectionFactory,
Logger logger,
int maxDegreeOfParallelism)
{
_connectionFactory = connectionFactory;
_logger = logger;
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
public async Task UploadDirectoryAsync(string localPath, string remotePath)
{
var files = Directory.GetFiles(localPath, "*", SearchOption.AllDirectories);
var tasks = new List<Task>();
var progress = new ConcurrentDictionary<string, bool>();
await Parallel.ForEachAsync(files, new ParallelOptions
{
MaxDegreeOfParallelism = _maxDegreeOfParallelism
}, async (localFile, ct) =>
{
try
{
using var client = _connectionFactory.CreateClient();
var relativePath = Path.GetRelativePath(localPath, localFile);
var remoteFilePath = CombineRemotePath(remotePath, relativePath);
await using var fileStream = File.OpenRead(localFile);
client.UploadFile(fileStream, remoteFilePath);
progress[localFile] = true;
_logger.LogInfo($"Uploaded: {localFile} -> {remoteFilePath}");
}
catch (Exception ex)
{
_logger.LogError($"Upload failed: {localFile} - {ex.Message}");
progress[localFile] = false;
}
});
GenerateReport(progress, "Upload");
}
public async Task DownloadDirectoryAsync(string remotePath, string localPath)
{
using var client = _connectionFactory.CreateClient();
var remoteFiles = client.ListDirectory(remotePath)
.Where(f => !f.IsDirectory)
.ToArray();
var progress = new ConcurrentDictionary<string, bool>();
await Parallel.ForEachAsync(remoteFiles, new ParallelOptions
{
MaxDegreeOfParallelism = _maxDegreeOfParallelism
}, async (remoteFile, ct) =>
{
try
{
using var client = _connectionFactory.CreateClient();
var localFilePath = Path.Combine(localPath, remoteFile.Name);
Directory.CreateDirectory(Path.GetDirectoryName(localFilePath)!);
await using var fileStream = File.Create(localFilePath);
client.DownloadFile(remoteFile.FullName, fileStream);
progress[remoteFile.FullName] = true;
_logger.LogInfo($"Downloaded: {remoteFile.FullName} -> {localFilePath}");
}
catch (Exception ex)
{
_logger.LogError($"Download failed: {remoteFile.FullName} - {ex.Message}");
progress[remoteFile.FullName] = false;
}
});
GenerateReport(progress, "Download");
}
private void GenerateReport(ConcurrentDictionary<string, bool> progress, string operation)
{
var successCount = progress.Count(p => p.Value);
var failureCount = progress.Count - successCount;
_logger.LogInfo($"{operation} Report: {successCount} succeeded, {failureCount} failed");
}
private static string CombineRemotePath(params string[] paths)
{
return string.Join("/", paths.Where(p => !string.IsNullOrEmpty(p)))
.Replace('\\', '/')
.Replace("//", "/");
}
}
public class SftpConnectionFactory
{
private readonly string _host;
private readonly int _port;
private readonly string _username;
private readonly string? _password;
private readonly string? _privateKeyPath;
public SftpConnectionFactory(string host, int port, string username, string? password, string? privateKeyPath)
{
_host = host;
_port = port;
_username = username;
_password = password;
_privateKeyPath = privateKeyPath;
}
public SftpClient CreateClient()
{
var connectionInfo = CreateConnectionInfo();
var client = new SftpClient(connectionInfo);
client.Connect();
return client;
}
private ConnectionInfo CreateConnectionInfo()
{
if (!string.IsNullOrEmpty(_privateKeyPath))
{
var keyFiles = new[] { new PrivateKeyFile(_privateKeyPath) };
return new ConnectionInfo(
_host,
_port,
_username,
new PrivateKeyAuthenticationMethod(_username, keyFiles));
}
return new ConnectionInfo(
_host,
_port,
_username,
new PasswordAuthenticationMethod(_username, _password));
}
}
public class Logger
{
private readonly string _logPath;
private readonly object _lock = new();
public Logger(string logPath)
{
_logPath = logPath;
Directory.CreateDirectory(Path.GetDirectoryName(_logPath)!);
}
public void LogInfo(string message)
{
WriteEntry("INFO", message);
}
public void LogError(string message)
{
WriteEntry("ERROR", message);
}
private void WriteEntry(string level, string message)
{
var entry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] [{Environment.CurrentManagedThreadId}] {message}";
lock (_lock)
{
File.AppendAllText(_logPath, entry + Environment.NewLine);
}
Console.WriteLine(entry);
}
}
配置文件示例(config.ini):
[SFTP]
Host = your.sftp.server
Port = 22
Username = your_username
Password = your_password
PrivateKeyPath =
LocalDirectory = C:\local\files
RemoteDirectory = /remote/files
Mode = Upload
MaxThreads = 4
[Logging]
LogPath = logs\transfer.log
实现特点:
- 配置管理:
- 使用Microsoft.Extensions.Configuration.Ini包解析INI配置
- 支持密码和私钥两种认证方式
- 可配置并发线程数(MaxThreads)
- 异步处理:
- 使用Parallel.ForEachAsync实现并行处理
- 每个文件操作使用独立的SFTP连接
- 严格限制最大并发数
- 日志系统:
- 线程安全的日志记录
- 包含时间戳、日志级别、线程ID和操作信息
- 同时输出到文件和控制台
- 异常处理:
- 单个文件操作失败不影响整体流程
- 详细的错误信息记录
- 最终生成传输结果报告
- 安全措施:
- 每个线程使用独立的SFTP连接
- 敏感信息隔离处理
- 完善的资源释放机制
- 目录处理:
- 支持目录递归操作
- 自动创建缺失的目录结构
- 保持相对路径结构
使用方法:
- 通过NuGet安装依赖包:
Install-Package SSH.NET
Install-Package Microsoft.Extensions.Configuration
Install-Package Microsoft.Extensions.Configuration.Ini
-
根据实际情况修改config.ini文件
-
根据需求选择上传或下载模式
-
查看生成的日志文件获取详细传输信息
程序会自动处理以下情况:
- 网络连接中断重试
- 文件权限问题
- 路径不存在自动创建
- 大文件传输
- 并发冲突处理
可以通过调整MaxThreads参数来优化性能,建议根据网络带宽和服务器性能进行测试后确定最佳并发数。