最可靠方式是用 FtpWebRequest 发送 MLSD 命令(失败时降级 LIST),显式设置 Method、Credentials 和 ISO-8859-1 编码解析响应,校验 StatusCode,注意路径格式与被动模式。

用 FtpWebRequest 列出 FTP 目录内容最可靠
直接调用 FtpWebRequest 是 C# 标准库中唯一原生支持 FTP 列目录的方式,WebClient 和 HttpClient 都不支持 LIST 或 MLSD 命令。别被网上某些“一行代码搞定”的误导骗了——那些基本是伪造响应或依赖第三方库。
关键点在于:必须显式设置 Method 为 "LIST" 或 "MLSD",且不能用 GET;否则服务器返回 500 错误或空响应。
-
LIST兼容老服务器,但解析依赖服务端格式(Unix 风格 / DOS 风格),需自行正则提取文件名 -
MLSD是 RFC 3659 标准,返回结构化数据(含时间、权限、类型),推荐优先使用,但部分旧 FTP 服务器不支持 - 务必设置
Credentials,匿名访问需用new NetworkCredential("", "") - 超时建议设为
Timeout = 30000,FTP 控制连接容易卡住
MLSD 响应解析要注意字段顺序和编码
MLSD 返回的是类似 modify=20231201102234;size=1024;type=file;name=report.pdf 的键值对行,每行末尾有换行符,且可能含非 ASCII 字符(如中文文件名)。常见坑是直接 Encoding.UTF8 读取导致乱码或截断。
- 先用
response.GetResponseStream()获取流,再用StreamReader指定Encoding.GetEncoding(ISO-8859-1)读取(FTP 协议规定 MLSD 元数据字段必须用 ISO-8859-1 编码) - 文件名在
name=后,但可能含=或分号,要用最后一个name=截取,并去除前后空格和引号 -
type=file表示文件,type=dir表示目录,别只靠扩展名判断
var request = (FtpWebRequest)WebRequest.Create("ftp://example.com/");
request.Method = "MLSD";
request.Credentials = new NetworkCredential("user", "pass");
using var response = (FtpWebResponse)request.GetResponse();
using var stream = response.GetResponseStream();
using var reader = new StreamReader(stream, Encoding.GetEncoding("ISO-8859-1"));
string line;
while ((line = reader.ReadLine()) != null) {
var nameMatch = Regex.Match(line, @"name=([^;]+)");
if (nameMatch.Success) {
string fileName = nameMatch.Groups[1].Value.Trim('"', ' ');
// 处理 fileName
}
}遇到 550 Access denied 或空列表先检查路径写法
FTP 路径不是 URL 路径:ftp://host/a/b/ 中的 /a/b/ 是服务器上的绝对路径(从根目录起),而 ftp://host//a/b/(双斜杠)可能被某些服务器解释为用户主目录下的 a/b。列出根目录就用 ftp://host/,列子目录必须带尾部斜杠,否则部分服务器返回 550。
技术上面应用了三层结构,AJAX框架,URL重写等基础的开发。并用了动软的代码生成器及数据访问类,加进了一些自己用到的小功能,算是整理了一些自己的操作类。系统设计上面说不出用什么模式,大体设计是后台分两级分类,设置好一级之后,再设置二级并选择栏目类型,如内容,列表,上传文件,新窗口等。这样就可以生成无限多个二级分类,也就是网站栏目。对于扩展性来说,如果有新的需求可以直接加一个栏目类型并新加功能操作
- 路径中避免反斜杠
\,全部用正斜杠/ - 用户名含
@符号时,URL 编码它(如user%40domain),否则WebRequest.Create解析失败 - 被动模式(PASV)是默认且必需的,若企业防火墙拦截 PASV 端口,需手动启用主动模式:
request.UsePassive = false,但极少需要 - 某些服务器要求先
CWD切换工作目录,再LIST,此时得发两次请求
别忽略 FtpWebResponse.StatusCode 和异常细节
FTP 错误不都抛 WebException,有些静默返回空流或错误状态码。比如服务器返回 530 Not logged in 时,response.StatusCode 是 FtpStatusCode.NotLoggedIn,但程序可能继续往下读空流,最终得到空列表。
- 始终检查
((FtpWebResponse)response).StatusCode是否为FtpStatusCode.CommandOK或FtpStatusCode.DataAlreadyOpen - 捕获
WebException后,读取e.Response再转FtpWebResponse,从中取StatusCode和StatusDescription - 调试时打印完整
StatusDescription,像"550 Permission denied"比泛泛的异常消息有用得多
真实环境里,FTP 服务器五花八门,有的连 MLSD 都没实现,有的返回 DOS 格式 LIST 输出却声称支持 MLSD。先试 MLSD,失败再降级到 LIST + 正则解析,比硬写一种方式靠谱得多。









