<p>FluentFTP 是 C# 中最稳定易维护的 FTP 开源库,支持连接复用、自动重连、UTF-8 文件名、内置目录递归及清晰错误分类,远优于原生 FtpWebRequest。</p>

FluentFTP 是目前 C# 中处理 FTP 最稳定、最易维护的开源库,比原生 FtpWebRequest 强太多——它默认复用连接、自动重连、支持 UTF-8 文件名、内置目录递归、错误分类清晰。如果你还在手写 FtpWebRequest 并反复调试 550 Permission denied 或中文乱码,换 FluentFTP 是最快止损方式。
为什么不能继续用 FtpWebRequest 做长连接
原生 FtpWebRequest 的 KeepAlive = true 只是“不主动断开”,但每次请求仍会新建控制连接(TCP 握手 + 登录认证),无法真正复用;更关键的是它不管理数据连接生命周期,高并发下极易触发服务器连接数限制或超时中断。实测在 Linux vsftpd 上,连续调用 10 次 ListDirectoryDetails 后,第 11 次大概率卡在 GetResponse() 超时。
常见表现包括:
WebException: The operation has timed outIOException: Unable to read data from the transport connection- 中文文件名变成问号或乱码(
Encoding.Default在不同系统上行为不一致)
FluentFTP 初始化与连接复用
核心是复用同一个 FtpClient 实例,而不是每次操作都 new 一个。它内部自带连接池和自动重连逻辑,只要不手动 Dispose() 或调用 Disconnect(),连接就一直活着。
推荐写法:
private static readonly FtpClient client = new FtpClient
{
Host = "ftp.example.com",
Username = "user",
Password = "pass",
DataConnectionType = FtpDataConnectionType.PASV,
Encoding = Encoding.UTF8,
ConnectTimeout = 10000,
ReadTimeout = 30000,
EnableThreadSafeDataChannels = true
};
<p>// 必须显式 Connect(),且只做一次
client.Connect();
注意点:
- 不要在方法内 new
FtpClient—— 连接开销大,且容易漏掉Dispose()导致句柄泄漏 -
DataConnectionType根据服务器类型选:PASV(被动模式)适用于大多数公网 FTP;PORT(主动模式)需客户端开放端口,内网调试可用 -
EnableThreadSafeDataChannels = true是多线程调用(如并行遍历子目录)的必要开关
递归列出所有文件(含路径、大小、时间)
FluentFTP 的 GetListing() 默认只列当前层,要递归得自己写逻辑。别用 GetListing(FTPLocation.Recursive) —— 它底层仍是逐层请求,且不返回完整路径,容易误判软链接或权限异常目录。
安全做法是手动 BFS 遍历:
var queue = new Queue<string>();
queue.Enqueue("/remote/path");
<p>while (queue.Count > 0)
{
var path = queue.Dequeue();
foreach (var item in client.GetListing(path))
{
if (item.Type == FtpObjectType.Directory && !item.FullName.EndsWith("/..") && !item.FullName.EndsWith("/."))
{
queue.Enqueue(item.FullName);
}
else if (item.Type == FtpObjectType.File)
{
Console.WriteLine($"{item.FullName} | {item.Size} | {item.Modified}");
}
}
}
关键细节:
-
item.FullName是带前缀的绝对路径(如/data/logs/app.log),不是相对名,可直接用于下载 - 必须过滤
/..和/.,否则某些 FTP 服务器会无限循环返回上级目录 -
item.Modified是服务器返回的原始时间戳,已按client.Encoding解析,无需再手动DateTime.Parse
读取文件内容而不下载到磁盘
想跳过临时文件、直接读取文本流?OpenRead() 返回 Stream,配合 StreamReader 即可:
using (var stream = client.OpenRead("/config/app.json"))
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string content = reader.ReadToEnd();
}
但要注意:
- 该流不支持
Seek,只能顺序读;若需多次读,得自己缓存content - 大文件(>50MB)慎用
ReadToEnd(),改用分块读(reader.Read(buffer, 0, buffer.Length))防内存暴涨 - 二进制文件(如图片、zip)请用
client.Download(<code>FileStream),别走OpenRead+BinaryReader,容易因编码/换行符处理出错
真正麻烦的从来不是“怎么连上”,而是“连上后怎么稳住”——FluentFTP 的 Connect() 和 GetListing() 看似简单,但背后每一步都在对抗 FTP 协议的松散定义和各服务器实现差异。别省那几行初始化代码,也别信“只要 KeepAlive=true 就算长连接”。连接复用、编码统一、路径规范化,三者缺一不可。


















