简介:这份C# FTP下载实例源码面向具备一定.NET基础的开发者与计算机专业学生,用于解决在C#项目中实现FTP文件传输的实际问题。资源围绕FtpWebRequest与FtpWebResponse两个核心类展开,涵盖连接服务器、凭据登录、被动模式与SSL加密设置、获取目录列表、流式读取响应数据并写入本地文件等完整环节,同时涉及异常处理、下载进度显示与多线程并行下载等进阶思路,适合作为网络编程入门到实践的参考范例。压缩包共21个文件,约65KB,以cs源码文件为主,配合sln解决方案、csproj工程文件、resx资源与settings配置,另有少量exe、pdb及缓存文件,结构完整可直接在Visual Studio中打开运行。目前已有693人学习下载,读者可借此理解FTP下载的基本原理,并在此基础上扩展断点续传、文件大小校验等功能,满足更复杂的应用场景。
1. 从 DFTPFile.sln 说起:一个能跑通的 C# FTP 下载实例长什么样
拿到一个叫C# FTP下载 实例源码(网络操作).rar的包,第一反应不是双击解压看代码,而是先判断它值不值得花时间。这个包的结构很直白:DFTPFile.sln是解决方案入口,DFTPFile.suo是 VS 的用户配置缓存,DFTPFile.csproj是工程文件,Program.cs是控制台入口,Form1.cs加Form1.designer.cs是 WinForm 界面,Properties放程序集信息,bin和obj是编译产物。也就是说,它同时给了控制台和窗体两条路,核心逻辑围绕FtpWebRequest和FtpWebResponse展开。
适合谁?正在做 C# 上位机、工控数据采集、设备日志回传,需要把远端 FTP 上的文件拉到本地的开发者。它不解决高并发断点续传,但把「连接—登录—列目录—下载—落盘—释放」这条链路完整走了一遍,拿来当网络操作的起点比看文档快。下面按「先立住原理,再动手复现,最后说坑」的顺序拆开讲。
2. 拆开 DFTPFile 工程:FtpWebRequest 的请求模型与连接参数
2.1 为什么是 FtpWebRequest 而不是 FtpClient
.NET 里做 FTP 下载有两条主流路线:一条是System.Net.FtpWebRequest,另一条是第三方库如 FluentFTP。这个实例选的是前者,原因很实际——它是 BCL 自带,不引 NuGet 包,在离线环境或老版本 .NET Framework 项目里直接能用。FtpWebRequest继承自WebRequest,用「创建请求 → 配置属性 → 取响应 → 读流」这套同步模型,理解成本低。
代价也要说清楚:FtpWebRequest在 .NET 6 之后被标记为过时,官方建议迁移到第三方库;它默认是同步阻塞的,做界面时容易卡 UI 线程;断点续传、连接池这些高级能力要自己补。所以这个实例的定位是「教学与轻量场景」,不是「生产级传输框架」。如果你的项目是 .NET Framework 4.x 的 WinForm 上位机,它完全够用;如果是 .NET 8 的新项目,建议只把它当原理参考。
2.2 连接四要素:URL、Method、Credentials、UsePassive
FTP 下载的第一步是把请求对象配好。下面这段是实例里最核心的骨架,我按可复现的方式重写并加了注释:
// 1. 创建请求,URL 指向具体文件而非目录 FtpWebRequest request = (FtpWebRequest)WebRequest.Create("ftp://192.168.1.10/data/log_20240101.txt"); // 2. 指定动作:下载文件用 DownloadFile,列目录用 ListDirectory request.Method = WebRequestMethods.Ftp.DownloadFile; // 3. 凭据:匿名登录用 anonymous,否则填真实账号 request.Credentials = new NetworkCredential("ftpuser", "ftp123456"); // 4. 被动模式:客户端发起数据连接,穿透 NAT 更稳 request.UsePassive = true; // 5. 二进制传输,避免文本模式换行符被改写 request.UseBinary = true; // 6. 超时:毫秒,默认无限等待,必须设 request.Timeout = 30000; request.ReadWriteTimeout = 30000;逻辑说明:WebRequest.Create返回的是基类,必须强转成FtpWebRequest才能设 FTP 专属属性。Method决定这次请求干什么,下载固定用DownloadFile。Credentials不设会走匿名,很多内网 FTP 恰恰禁匿名,这是新手第一个翻车点。UsePassive建议恒为true,主动模式下服务器要反向连客户端,客户端在 NAT 或防火墙后就断。UseBinary对日志、压缩包、图片必须开,否则\n会被转成\r\n,文件校验直接对不上。
参数怎么改:Timeout按网络质量给,内网 10 秒够,跨机房给 30 到 60 秒;ReadWriteTimeout管的是读写流阶段的等待,别只设Timeout忘了它。EnableSsl默认false,只有服务器明确要求 FTPS 才设true,设错会直接抛异常。
2.3 列目录与下载的请求差异
实例里Form1.cs承担了「浏览服务器文件」的交互,靠的是把Method换成ListDirectory:
FtpWebRequest listReq = (FtpWebRequest)WebRequest.Create("ftp://192.168.1.10/data/"); listReq.Method = WebRequestMethods.Ftp.ListDirectoryDetails; // 带权限、大小、日期 listReq.Credentials = new NetworkCredential("ftpuser", "ftp123456"); listReq.UsePassive = true; using (FtpWebResponse listResp = (FtpWebResponse)listReq.GetResponse()) using (StreamReader reader = new StreamReader(listResp.GetResponseStream())) { string line; while ((line = reader.ReadLine()) != null) { // 每行形如:-rw-r--r-- 1 owner group 10240 Jan 01 10:00 log.txt Console.WriteLine(line); } }ListDirectory只返回文件名,ListDirectoryDetails返回 Unix 风格的长格式,含大小和修改时间。要解析大小做进度显示,就得用后者,然后按空格切分。注意不同 FTP 服务端(IIS FTP、vsftpd、FileZilla Server)返回的格式有细微差别,硬编码列索引容易在换服务器时崩,稳妥做法是用正则匹配日期和大小字段。
3. 把文件真正落到本地:流复制、缓冲区与资源释放
3.1 响应流到文件流的复制循环
下载的本质是把网络流的数据搬进本地文件流。实例用的是经典的定长缓冲区循环:
FtpWebResponse response = (FtpWebResponse)request.GetResponse(); Stream responseStream = response.GetResponseStream(); using (FileStream localFile = new FileStream(@"D:\download\log_20240101.txt", FileMode.Create, FileAccess.Write)) { byte[] buffer = new byte[8192]; // 8KB 缓冲区 int bytesRead; while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) { localFile.Write(buffer, 0, bytesRead); } } responseStream.Close(); response.Close();逻辑说明:GetResponse()这一步才真正发起网络连接,前面的属性配置此时生效。GetResponseStream()拿到的是只读网络流,Read返回 0 表示读完。缓冲区大小是性能关键,实例里给的是 1024,我一般改成 8192 或 65536,大文件下吞吐差别明显。FileMode.Create会覆盖同名文件,要追加或续传得换Append或先判断存在。
参数说明:缓冲区不是越大越好,超过 64KB 对吞吐提升有限,反而占内存;FileStream的FileAccess.Write明确只写,避免误读。写完必须关流,using包住本地流,网络流和响应手动关,顺序是「先本地后网络」,反了在某些服务端会报连接被重置。
3.2 进度反馈:从 ContentLength 到进度条
WinForm 里要显示进度,得先知道文件总大小。FtpWebResponse.ContentLength在服务端支持时返回字节数:
long total = response.ContentLength; long received = 0; int bytesRead; while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) { localFile.Write(buffer, 0, bytesRead); received += bytesRead; if (total > 0) { int percent = (int)(received * 100 / total); // 跨线程更新 UI,别在下载线程直接改控件 progressBar1.Invoke(new Action(() => progressBar1.Value = percent)); } }ContentLength为 -1 表示服务端没给大小,这时进度条只能做「已下载字节数」的滚动显示,不能算百分比。跨线程更新控件是 WinForm 的硬规矩,直接赋值会抛「线程间操作无效」,Invoke或BeginInvoke二选一,前者同步等待,后者异步投递。
3.3 异常处理与资源释放的完整包裹
网络操作最怕异常把流泄漏了。实例的异常处理偏简单,实际用要包成这样:
FtpWebResponse response = null; Stream responseStream = null; FileStream localFile = null; try { response = (FtpWebResponse)request.GetResponse(); responseStream = response.GetResponseStream(); localFile = new FileStream(localPath, FileMode.Create, FileAccess.Write); // ... 复制循环 } catch (WebException ex) { // ex.Status 能区分超时、文件不存在、认证失败 Console.WriteLine($"FTP 错误:{ex.Status} - {ex.Message}"); } finally { localFile?.Close(); responseStream?.Close(); response?.Close(); }WebException.Status是排查利器:TimedOut是超时,ProtocolError多半是文件不存在或权限不足,TrustFailure是 SSL 证书问题。把状态码打出来,比只看Message快得多。finally里用空条件运算符兜底,保证任何路径下资源都释放。
4. 避坑与排查:FTP 下载最常见的五类翻车
4.1 现象:连接一直卡住直到超时
原因:UsePassive没设或设成false,客户端在 NAT 后,服务端主动回连的端口被防火墙挡了。解决:强制request.UsePassive = true,并确认服务端被动端口范围在防火墙放行。内网测试如果主动模式能通、被动不通,多半是服务端被动端口没配。
4.2 现象:下载的文本文件多出空行或乱码
原因:UseBinary没开,FTP 默认文本模式把\n转成\r\n。解决:request.UseBinary = true。反过来,如果确实要按文本处理且服务端是 Unix,也要显式设false并自己处理换行,别依赖默认值。
4.3 现象:大文件下载到一半抛「连接被远程主机强迫关闭」
原因:Timeout和ReadWriteTimeout只设了一个,或者服务端有闲置超时。解决:两个都设,且ReadWriteTimeout不小于单次读写的预期耗时;服务端侧调大闲置超时。这个报错在热词里也常被提到,本质是长连接被中途掐断,不是代码逻辑错。
4.4 现象:中文文件名下载后变成乱码或找不到文件
原因:FTP 协议本身对文件名编码没有统一规定,FtpWebRequest默认按 UTF-8 解析,但很多服务端用 GBK。解决:优先让服务端统一 UTF-8;改不了就在拼接 URL 时对文件名做Uri.EscapeDataString编码,或改用支持指定编码的第三方库。这是FtpWebRequest的已知短板。
4.5 现象:下载完文件大小对但内容损坏
原因:复制循环里localFile.Write的偏移或长度写错,或者缓冲区被复用前没清。解决:确认Write(buffer, 0, bytesRead)三个参数正确,bytesRead是本次实际读到的字节数,不能写buffer.Length。另外确认FileMode不是Append导致旧内容叠加。
5. 从能跑到好用:断点续传与多文件队列的落地技巧
基础下载跑通后,真正拉开差距的是断点续传。FtpWebRequest支持通过ContentOffset指定从文件的哪个字节开始下:
long localSize = new FileInfo(localPath).Exists ? new FileInfo(localPath).Length : 0; request.ContentOffset = localSize; // 从已下载位置继续 // 本地流用 Append 模式,不覆盖已有内容 localFile = new FileStream(localPath, FileMode.Append, FileAccess.Write);ContentOffset告诉服务端跳过前 N 字节,本地用Append接着写。前提是服务端支持REST命令,IIS FTP 和 vsftpd 都支持,少数简易服务端不支持会忽略偏移,导致文件错位,所以续传前最好用ListDirectoryDetails核对远端文件大小,和本地已下大小比对后再决定偏移量。
多文件队列我一般用一个简单的生产者消费者:ListDirectoryDetails解析出待下文件列表塞进Queue<string>,开一个后台线程循环出队下载,下载完再取下一个。不要为每个文件开一个线程去并发,FTP 服务端通常限制单账号连接数,并发过高会被踢,反而更慢。真要提速,控制在 2 到 3 个并发,并且每个连接独立FtpWebRequest。
验证下载是否可靠,别只看文件存在。我的习惯是下载完成后比对远端和本地的字节数,字节数一致再算成功;对压缩包再跑一次解压校验,对文本比对首尾行。这套动作多花几秒,但能挡住「大小对、内容坏」这种最坑的情况。从那以后我每次做 FTP 拉取,都强制走一遍「大小比对 + 抽样校验」,再没被静默损坏坑过。希望帮到你。
本文还有配套的精品资源,点击获取