FTP协议不支持事件通知,FileSystemWatcher无法监控远程FTP目录,只能通过轮询实现;应优先使用MLSD命令获取标准化文件信息,结合文件路径、时间戳和大小三元组比对,并过滤临时文件,合理设置轮询间隔与超时重试机制。

为什么不能直接监听FTP目录变化
FTP协议本身不提供文件系统事件通知机制,FileSystemWatcher 对远程FTP路径完全无效——它只作用于本地挂载或映射的驱动器,而绝大多数FTP服务器不会让客户端挂载为本地盘符。试图用FileSystemWatcher 监控 ftp://example.com/inbox/ 会静默失败或抛出NotSupportedException。
真正可行的方案只有轮询(polling):定期连接FTP服务器,列出目录内容,比对上次结果。关键在于「怎么比对才可靠」和「怎么避免误判」。
用FtpWebRequest列出文件并提取时间戳
FtpWebRequest 是.NET Framework原生支持的方式,但要注意:不同FTP服务器返回的LIST格式差异极大(UNIX风格、DOS风格、甚至自定义),直接解析文本极不稳定。更稳妥的做法是用MLSD命令(RFC 3659),它返回标准化的机器可读格式,包含modify、size、type等字段。
- 必须显式设置
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails(对应MLSD) - 若服务器不支持
MLSD(如某些老IIS FTP),降级用ListDirectory(LIST),但需针对该服务器日志样本写专用解析器 -
response.GetResponseStream()返回的每行形如type=file;size=12345;modify=20240520103022; /report.pdf,用分号分割后取modify值转为DateTime
如何安全比对两次轮询结果
仅靠文件名判断新增容易出错:同名文件覆盖、上传中断重传、临时文件(如.part或~$前缀)都可能干扰。必须结合时间戳与大小:
- 记录每次轮询后每个文件的
fullPath + lastWriteTime + size三元组,存入内存字典或轻量数据库(如SQLite) - 下次轮询时,对每个新文件检查:是否在历史记录中不存在,或存在但
lastWriteTime更新且size变大(排除覆盖场景) - 跳过以
.开头的文件(隐藏文件)、.tmp/.part后缀文件(未完成上传) - 避免“假新增”:同一文件因网络重传被列两次,需在FTP服务器端确认是否支持
MDTM命令校验最后修改时间一致性
轮询间隔与资源控制的实际权衡
高频轮询(如5秒)易被服务器限流或触发防火墙规则;低频轮询(如5分钟)则延迟高。折中策略取决于业务容忍度:
- 初始阶段设为30秒,观察
FtpWebResponse.StatusCode是否频繁出现530 Not logged in或421 Too many connections - 每次请求后务必调用
response.Close()和request.Abort(),否则连接池耗尽会导致后续请求卡死 - 用
System.Threading.Timer而非Task.Delay().Wait(),避免阻塞线程池线程 - 若需监控多个FTP站点,每个站点独立计时器+独立连接凭据,避免单点故障扩散
真正麻烦的是被动模式(PASV)下NAT/防火墙导致的数据连接超时,这需要在FtpWebRequest上设置ReadWriteTimeout和Timeout,并捕获WebException中Status == WebExceptionStatus.Timeout做重试,而非直接崩溃。










