当前位置: 首页 > news >正文

优化后的 FtpClient 代码

优化说明问题解决:

  1. 目录已存在报错:增强错误处理,忽略多种目录已存在状态码。
  2. UI 卡顿:移除 Thread.Sleep,用 async/await 和并行任务,线程安全更新 UI。
  3. 异步上传:限制并发任务(5),提升效率。
  4. 安全性:支持证书指纹验证,强制 TLS 1.2/1.3。
  5. 用户体验:禁用按钮防重复点击,显示进度,弹窗提示。

优化后的 FtpClient 代码:csharp

using System; using System.IO; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace FTP客户端Demo { public class FtpClient { private readonly string _host, _username, _password, _trustedThumbprint; private readonly bool _useFtps; public FtpClient(string host, string username, string password, bool useFtps = true, string trustedThumbprint = null) { _host = host.TrimEnd('/'); _username = username; _password = password; _useFtps = useFtps; _trustedThumbprint = trustedThumbprint; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; } public async Task UploadFileAsync(string localFilePath, string remoteFilePath, bool createDirectories = true, IProgress<int> progress = null) { if (!File.Exists(localFilePath)) throw new FileNotFoundException($"本地文件 {localFilePath} 不存在"); var request = (FtpWebRequest)WebRequest.Create($"{_host}/{remoteFilePath}"); request.Method = WebRequestMethods.Ftp.UploadFile; request.Credentials = new NetworkCredential(_username, _password); request.UseBinary = true; request.UsePassive = true; request.KeepAlive = false; if (_useFtps) { request.EnableSsl = true; ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate; } if (createDirectories) { var directoryPath = Path.GetDirectoryName(remoteFilePath)?.Replace("\\", "/"); if (!string.IsNullOrEmpty(directoryPath)) await CreateDirectoryAsync(directoryPath); } using (var fileStream = File.OpenRead(localFilePath)) using (var requestStream = await request.GetRequestStreamAsync()) { byte[] buffer = new byte[8192]; long totalBytes = fileStream.Length, bytesSent = 0; int bytesRead; while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await requestStream.WriteAsync(buffer, 0, bytesRead); bytesSent += bytesRead; progress?.Report((int)(bytesSent * 100 / totalBytes)); } } using (var response = (FtpWebResponse)await request.GetResponseAsync()) Console.WriteLine($"上传成功: {remoteFilePath}"); } private async Task CreateDirectoryAsync(string directoryPath) { var directories = directoryPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); var currentPath = string.Empty; foreach (var dir in directories) { currentPath = $"{currentPath}/{dir}"; var request = (FtpWebRequest)WebRequest.Create($"{_host}{currentPath}"); request.Method = WebRequestMethods.Ftp.MakeDirectory; request.Credentials = new NetworkCredential(_username, _password); request.UsePassive = true; request.KeepAlive = false; if (_useFtps) { request.EnableSsl = true; ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate; } try { using (var response = (FtpWebResponse)await request.GetResponseAsync()) Console.WriteLine($"创建目录 {currentPath}"); } catch (WebException ex) { if (((FtpWebResponse)ex.Response)?.StatusCode is FtpStatusCode.ActionNotTakenFileUnavailable or FtpStatusCode.FileActionNotTaken) continue; throw; } } } private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors == SslPolicyErrors.None) return true; if (!string.IsNullOrEmpty(_trustedThumbprint) && certificate.GetCertHashString().Replace(" ", "").ToUpper() == _trustedThumbprint.ToUpper()) return true; if (chain != null) { chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; if (chain.Build(new X509Certificate2(certificate)) && chain.ChainStatus.Length == 0) return true; } return false; } } }

优化后的主程序:csharp

using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace FTP客户端Demo { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async void btnUpload_Click(object sender, EventArgs e) { string ftpserver = tbftpServer.Text; string remoteFilePath = tbfilePath.Text; string username = tbusername.Text; string password = tbpassword.Text; string localFilePath = txtLocalFilePath.Text; string trustedThumbprint = tbTrustedThumbprint.Text; if (string.IsNullOrEmpty(ftpserver) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(localFilePath) || string.IsNullOrEmpty(remoteFilePath)) { MessageBox.Show("请填写所有必填字段!", "错误"); return; } try { var ftpClient = new FtpClient(ftpserver, username, password, true, trustedThumbprint); btnUpload.Enabled = false; label5.Text = "0 / 100"; int maxConcurrentUploads = 5, completedCount = 0; var tasks = new Task[maxConcurrentUploads]; for (int i = 0; i < 100; i++) { var remoteFilePathNew = $"{remoteFilePath}/{i}.bmp"; var progress = new Progress<int>(_ => Invoke((Action)(() => label5.Text = $"{completedCount} / 100"))); int taskIndex = Task.WaitAny(tasks); if (taskIndex >= 0) tasks[taskIndex] = null; tasks[taskIndex] = Task.Run(async () => { try { await ftpClient.UploadFileAsync(localFilePath, remoteFilePathNew, true, progress); Interlocked.Increment(ref completedCount); } catch (Exception ex) { Invoke((Action)(() => MessageBox.Show($"上传 {remoteFilePathNew} 失败: {ex.Message}", "错误"))); } }); if (i < maxConcurrentUploads) await Task.Delay(100); } await Task.WhenAll(tasks.Where(t => t != null)); MessageBox.Show("上传完成!", "成功"); } catch (Exception ex) { MessageBox.Show($"上传失败: {ex.Message}", "错误"); } finally { btnUpload.Enabled = true; } } } }

使用:

  • 控件:tbftpServer, tbfilePath, tbusername, tbpassword, txtLocalFilePath, tbTrustedThumbprint (TextBox), label5 (Label), btnUpload (Button)。
  • 运行:输入 FTP 信息,点击上传,label5 显示进度(如 “50 / 100”)。
  • 要求:.NET Framework 4.8 或 .NET 6+,FTPS 服务器,证书指纹(测试可选)。

建议:

  • 添加重试机制。
  • 用 ProgressBar 替换 label5。
  • 集成 Serilog 日志。
  • 考虑 SFTP(SSH.NET)以提升安全性。

如需 SFTP 或其他优化,请告知!

http://www.jsqmd.com/news/800144/

相关文章:

  • Model2Vec最佳实践:10个技巧让你的嵌入模型又快又好
  • Radon配置详解:从pyproject.toml到自定义规则
  • 终极Voron 2.4高速3D打印机:从零开始构建专业级CoreXY打印机的完整指南
  • 潜变量模型完全指南:从高斯混合模型到变分自编码器
  • Graphpack Performance Monitor Plugin
  • 终极指南:如何用Chromatic快速掌握Chromium/V8通用修改器
  • Paper2Agent教程执行器深度解析:如何确保研究代码的可重现性
  • 现代UI组件库SyntaxUI:基于React与Tailwind CSS的快速开发实践
  • 别再只用电阻限流了!手把手教你用PMOS和比较器搭建一个更快的软启动电路(附0.2欧姆采样电阻选型)
  • AI开发环境一键配置:从CUDA到Docker的自动化实践
  • GTA5线上小助手:终极免费工具完整使用指南,快速提升游戏体验
  • 如何高效获取百度文库文档:免费打印与保存的完整指南
  • 宇宙学模拟中的AMR技术挑战与cuRAMSES优化方案
  • 量子纠错码缺陷处理方案比较与优化
  • 从零构建现代化应用托管平台:K3s与云原生技术栈实战指南
  • FreeRTOS在RISC-V上的心跳:深入剖析vPortSetupTimerInterrupt函数与mtime机制
  • AsyncRun.vim 项目根目录管理:智能识别和高效利用
  • CVAT标注实战:用‘追踪模式’高效处理视频目标检测任务
  • Blueprint3D开发指南:深入理解Three.js室内设计引擎
  • Midjourney V6油彩风格实战手册:从提示词结构、--s 250–400区间精调到画布比例适配的12个避坑公式
  • 【企业管理】企业全岗位综合运营与组织知识矩阵体系——18 管理科学之管理者常见场景和模式、管理者奖金分配、收入分配与绩效评估、权力——利益矩阵
  • 告别BPG!用自回归+分层先验模型手把手复现图像压缩SOTA(附PyTorch核心代码解析)
  • GCanvas与HTML5 Canvas对比:为什么选择跨平台图形引擎
  • 蒙特卡洛方法赋能智能体决策:原理、实现与工程实践
  • AlpacaEval自定义评估器开发教程:从零开始构建专属评估器
  • Video-Use部署与配置:在多平台AI代理中集成视频编辑技能的最佳实践
  • 不只是拧螺丝:拆解F450无人机硬件组装背后的工程思维(电机/电调/飞控协同)
  • 想进大厂?除了刷题,这些‘软技能’和‘信息差’才是关键(以网易杭研为例)
  • 从音频处理到IoT数据:用scipy.signal.resample_poly搞定实际项目中的采样率转换
  • Excel高效使用技巧(十五):终极技巧汇总:高级玩家必备的邪修操作