在C#项目里操作FTP服务器是不少业务场景的刚需,比如报表文件的定时归档、图片资源的批量同步、日志文件的集中采集等。.NET框架虽然内置了FtpWebRequest类,但它的API设计比较陈旧,连最基础的列表获取都要拼命令字符串,异步模型也停留在APM时代,写起来费劲还容易出坑。FluentFTP是一个专门为.NET设计的开源FTP客户端库,API简洁流畅,支持批量传输、目录同步、断点续传、进度回调等高级功能,是目前C#操作FTP的首选方案。本文从零开始讲解FluentFTP的用法,覆盖连接、上传、下载、管理四大核心场景。

一、两种方案对比:为什么推荐FluentFTP
先说.NET自带的方案。FtpWebRequest位于System.Net命名空间下,从.NET Framework 2.0时代就存在了。它能完成基本的增删改查,但几乎所有操作都要手动构造WebRequestMethod常量,处理响应流,还要自己处理编码、被动模式、超时等细节。更麻烦的是,微软官方已明确表示不再为这个类添加新特性,在.NET 6及以后的版本中它只是被标记为过时但保留兼容,异步支持非常有限。
FluentFTP则是社区维护的活跃库,GitHub上持续更新,同时支持.NET Framework 4.6.2以上和.NET Standard 2.0,意味着.NET Core、.NET 5/6/7/8全部适用。它的优势可以总结为四点:第一,链式配置的连接方式,几行代码就能建立连接;第二,原生支持async/await异步模型,适合WinForm、WPF或Web项目;第三,内置文件列表解析器,能兼容Serv-U、vsftpd、FileZilla等不同FTP服务端的列表格式;第四,支持FTPS(FTP over TLS)加密传输,安全性有保障。
安装方式很简单,通过NuGet即可完成:
Install-Package FluentFTP // 或者使用.NET CLI dotnet add package FluentFTP
二、建立连接与基本配置
使用FluentFTP的第一步是创建AsyncFtpClient实例(推荐使用异步版本)。连接时需要指定主机地址、端口、用户名和密码,还可以通过config配置加密模式、编码、超时时间等参数。下面的代码演示了一个完整的连接过程,包括连接检测和资源释放:
using FluentFTP;
// 创建异步FTP客户端
var client = new AsyncFtpClient("192.168.0.1", "admin", "password", 21);
// 可选配置
client.Config.EncryptionMode = FtpEncryptionMode.Explicit; // 启用FTPS显式加密
client.Config.ConnectTimeout = 10000; // 连接超时10秒
client.Config.DataConnectionEncoding = System.Text.Encoding.UTF8; // 中文文件名编码
client.Config.RetryAttempts = 3; // 失败自动重试3次
// 建立连接
await client.Connect();
// 检测连接状态
if (client.IsConnected)
{
Console.WriteLine("FTP连接成功");
// 获取服务器系统信息
var sysInfo = await client.GetSystemInfo();
Console.WriteLine($"服务器系统: {sysInfo.ServerOS}");
}
// 操作完成后断开
await client.Disconnect();
client.Dispose();有几个配置项值得注意。EncryptionMode设为Explicit表示使用显式FTPS,客户端在21端口建立普通连接后再升级为TLS加密,这是最常见的加密FTP方式;如果服务端要求隐式加密,则改为Implicit并连接990端口。Encoding问题在中文环境下尤其重要,很多FTP服务端默认使用GBK编码,如果不显式指定UTF8或GBK,中文文件名会出现乱码,甚至导致找不到文件。另外建议把操作逻辑放在using代码块中,或者用try-finally确保连接释放,避免句柄泄漏。
三、文件上传与下载:单文件和批量操作
FluentFTP提供了同步和异步两套API,方法名一致,只是异步版本带Async后缀。单文件上传用UploadFile,下载用DownloadFile,它们都返回一个布尔值表示传输结果。来看实际的代码示例:
// 上传单个文件,覆盖同名文件
bool uploadResult = await client.UploadFile(
@"C:\Reports\daily.xlsx", // 本地路径
"/backup/daily.xlsx", // 远程路径
FtpRemoteExists.Overwrite, // 存在则覆盖
true // 验证文件校验和
);
// 下载单个文件
bool downloadResult = await client.DownloadFile(
@"C:\Downloads\report.xlsx",
"/backup/daily.xlsx",
FtpLocalExists.Overwrite,
FtpVerify.Retry // 校验失败时自动重试
);
// 批量上传整个目录,自动创建远程目录并递归
var results = await client.UploadDirectory(
@"C:\LocalData", // 本地源目录
"/remote/data", // 远程目标目录
FtpFolderSyncMode.Mirror, // 镜像模式:删除远程多余文件
FtpRemoteExists.Overwrite,
FtpVerify.None,
Rules.RecursiveExclude(".tmp") // 排除临时文件
);
// 批量下载整个目录
await client.DownloadDirectory(
"/remote/data",
@"C:\LocalData",
FtpFolderSyncMode.Update // 只下载有变动的文件
);
// 查看批量传输统计
Console.WriteLine($"上传成功 {results.Files.Count()} 个文件");批量操作中FtpFolderSyncMode参数很关键。Update模式只传输修改时间有变化的文件,适合定期增量备份;Mirror模式会把远程目录变成与本地完全一致的镜像,远程多出来的文件会被删除,做发布部署时很方便,但使用前要想清楚是否接受删除行为。如果只需要批量传输一批指定文件而非整个目录,可以用UploadFiles方法传入文件路径数组,它内部会复用连接,比循环调用UploadFile快得多。
传输进度监控也是实际项目的常见需求,比如在界面上显示进度条。FluentFTP支持在每个传输方法最后传入一个进度回调委托,回调中能拿到已传输字节数和总字节数:
Progress<FtpProgress> progress = new Progress<FtpProgress>(p =>
{
// p.Progress为0到1的进度值,p.TransferSpeedToString为速度描述
Console.WriteLine($"当前进度: {p.Progress * 100:F1}% 速度: {p.TransferSpeedToString()}");
});
await client.UploadFile(
@"C:\Videos\demo.mp4",
"/media/demo.mp4",
FtpRemoteExists.Overwrite,
false,
FtpVerify.None,
progress
);四、目录遍历与远程文件管理
除了传输,FluentFTP对远程文件系统的管理能力也很完善。获取目录列表、判断文件存在性、创建删除目录、移动重命名文件都有对应方法,看下面这段综合示例:
// 获取目录下的文件和文件夹列表
FtpListItem[] items = await client.GetListing("/remote/data");
foreach (var item in items)
{
Console.WriteLine($"名称: {item.Name}, 类型: {item.Type}, 大小: {item.Size}字节");
}
// 判断文件或目录是否存在
bool fileExists = await client.FileExists("/remote/data/config.json");
bool dirExists = await client.DirectoryExists("/remote/data");
// 创建目录(支持递归创建多级)
await client.CreateDirectory("/remote/data/2024/archives", true);
// 删除文件和目录
await client.DeleteFile("/remote/data/old.log");
await client.DeleteDirectory("/remote/data/temp"); // 递归删除整个目录
// 移动或重命名
await client.MoveFile("/remote/data/a.txt", "/remote/data/b.txt");
await client.MoveDirectory("/old/path", "/new/path");
// 获取文件大小和修改时间
long size = await client.GetFileSize("/remote/data/config.json");
DateTime modified = await client.GetModifiedTime("/remote/data/config.json");处理GetListing返回的FtpListItem时,要注意Type属性的判断。值为FtpObjectType.File表示普通文件,FtpObjectType.Directory表示目录,FtpObjectType.Link表示符号链接。如果要做递归遍历整棵目录树,可以自己封装一个递归方法,也可以使用库提供的GetListing配合manual递归实现。一个实用的技巧是:上传前先判断远程目录是否存在并用CreateDirectory的force参数自动补齐路径,这样批量上传就不必预先手动建目录了。
五、断点续传与异常处理的实战建议
大文件传输中途断网是常事,FluentFTP支持断点续传。上传时使用FtpRemoteExists.AddResume,下载时使用FtpLocalExists.Resume,客户端会自动从上次中断的位置继续。再配合config中的RetryAttempts和重连逻辑,可以构建出相当健壮的传输方案:
// 支持断点续传的上传
await client.UploadFile(
@"C:\BigData\dataset.zip",
"/backup/dataset.zip",
FtpRemoteExists.AddResume, // 断点续传模式
true
);
// 封装带重连的传输方法
async Task<bool> UploadWithRetry(string localPath, string remotePath, int maxRetry = 3)
{
for (int i = 0; i < maxRetry; i++)
{
try
{
if (!client.IsConnected)
await client.Connect();
return await client.UploadFile(localPath, remotePath, FtpRemoteExists.Overwrite);
}
catch (FtpException ex)
{
Console.WriteLine($"第{i + 1}次传输失败: {ex.Message}");
await Task.Delay(3000); // 等待3秒后重试
}
}
return false;
}异常处理方面,FluentFTP把所有错误都包装成FtpException及其子类,比如FtpAuthenticationException表示认证失败,FtpCommandException表示服务端返回错误码。捕获时建议先判断具体子类型,认证错误直接终止重试,临时性网络错误才值得重试。另外如果是在ASP.NET Core这类服务端项目中使用,可以考虑把AsyncFtpClient注册为单例并配合AutoConnect自动重连,避免每次请求都握手建连带来的性能损耗。对于定时批量同步任务,则建议每次任务执行完主动Disconnect,下次任务开始时重新连接,这样更稳定可控。
总结一下,FluentFTP把FTP操作从繁琐的底层细节中解放出来,从单文件传输到目录镜像同步,从进度监控到断点续传,几乎覆盖了所有常见需求。掌握连接配置、批量传输、文件管理和异常重试这几个核心点,就能在绝大多数C#项目中稳定地完成FTP文件管理任务。如果项目还在用FtpWebRequest拼字符串,不妨迁移到FluentFTP,迁移成本不高,代码可读性和稳定性都会有明显提升。