C如何上传文件到FTP服务器?,FTP上传文件报错怎么办?

用C#上传文件到FTP服务器,最直接的方法是使用System.Net.FtpWebRequest类,通过创建FTP请求、写入文件流并获取响应来完成,整个流程大约十几行核心代码就能跑通。如果你正在折腾文件同步或备份功能,这篇文章会一步步讲透从基础上传到断线重传的完整思路。

C# FTP上传的核心逻辑与准备工作

FTP上传说白了就是把本地文件通过FTP协议推送到远程服务器,C#里没有比FtpWebRequest更原生的方案了,它属于.NET框架自带的能力,不需要额外安装NuGet包,在控制台、WinForm、ASP.NET Core里都能用。

FTP数据传输故障排错案例讲解
加载中
FTP数据传输故障排错案例讲解
陈
陈师傅的安全工坊
4479--
原视频地址

动手写代码前,先确认三件事:

  • 服务器地址要带上协议头,比如ftp://192.168.1.100/upload/,别漏了ftp://前缀。
  • 如果服务器路径对应的是子目录,先确认目录存在,或者代码里写创建目录的逻辑。
  • FTP账号密码建议从配置文件读取,别硬编码在源码里,不然以后换密码还得重新编译。

核心思路分四步:建立连接、设置请求类型、把文件字节写入请求流、等待服务器返回状态,下面看具体代码。

最简实现:用FtpWebRequest逐步上传

假设你本地有个D:\temp\report.pdf,要传到ftp://192.168.1.100/files/report.pdf,账号是user,密码是pass,直接看这段:

using System;
using System.IO;
using System.Net;
public static void UploadFile(string localPath, string remotePath, string ftpHost, string ftpUser, string ftpPass)
{
    string fullUri = ftpHost + remotePath;
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(fullUri);
    request.Method = WebRequestMethods.Ftp.UploadFile;
    request.Credentials = new NetworkCredential(ftpUser, ftpPass);
    request.UseBinary = true;
    request.UsePassive = true;
    request.KeepAlive = false;
    byte[] fileData = File.ReadAllBytes(localPath);
    request.ContentLength = fileData.Length;
    using (Stream reqStream = request.GetRequestStream())
    {
        reqStream.Write(fileData, 0, fileData.Length);
    }
    using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
    {
        Console.WriteLine($"上传完成,状态:{response.StatusDescription}");
    }
}

注意几个细节:

  • UseBinary = true表示二进制传输,传图片、压缩包、PDF必须设成true,否则文件会损坏。
  • UsePassive = true走被动模式,大多数服务器都支持,主动模式容易被防火墙卡住。
  • KeepAlive = false每次上传完就断开连接,避免连接池占着不放。

这段代码能跑,但有个小问题:File.ReadAllBytes会把整个文件加载进内存,传大文件(比如几个GB的视频)内存直接爆掉,所以下面给出流式上传的写法。

大文件流式上传:不占内存的写法

把文件切成小块,边读边写,内存占用几乎恒定:

public static void UploadFileStream(string localPath, string remoteUrl, NetworkCredential cred)
{
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(remoteUrl);
    request.Method = WebRequestMethods.Ftp.UploadFile;
    request.Credentials = cred;
    request.UseBinary = true;
    request.UsePassive = true;
    request.KeepAlive = false;
    request.Timeout = 30000;
    using (FileStream fs = File.OpenRead(localPath))
    {
        request.ContentLength = fs.Length;
        using (Stream reqStream = request.GetRequestStream())
        {
            byte[] buffer = new byte[8192]; // 8KB缓冲块
            int bytesRead;
            while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
            {
                reqStream.Write(buffer, 0, bytesRead);
            }
        }
    }
    using (var resp = (FtpWebResponse)request.GetResponse())
    {
        Console.WriteLine($"状态码:{(int)resp.StatusCode}");
    }
}

这里用File.OpenRead打开文件流,请求流是网络流,两个流之间用固定大小的缓冲搬运数据,CPU和内存都稳,业内专家指出,大部分FTP服务器对单次连接的超时限制在30秒左右,所以Timeout设置成30000毫秒是比较稳妥的起步值。

上传目录自动创建与文件名冲突处理

很多实际场景中,远程目录是按日期或业务ID动态生成的,比如ftp://xxx/2026/04/12/,FTP协议本身没有“创建目录”的标准命令,但可以通过MakeDirectory方法实现。

递归创建远程目录的实用技巧

思路很简单:把远程路径按切段,一段一段检查,不存在就创建,不过服务器不提供判断目录是否存在的直接接口,通常用GetFileSize或ListDirectory试探,抛出异常就说明不存在。

public static void EnsureRemoteDirectory(string ftpHost, string directoryPath, NetworkCredential cred)
{
    string[] segments = directoryPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
    string current = ftpHost.TrimEnd('/');
    foreach (string seg in segments)
    {
        current += "/" + seg;
        if (!DirectoryExists(current, cred))
        {
            FtpWebRequest req = (FtpWebRequest)WebRequest.Create(current);
            req.Method = WebRequestMethods.Ftp.MakeDirectory;
            req.Credentials = cred;
            using (req.GetResponse()) { }
        }
    }
}
private static bool DirectoryExists(string dirUrl, NetworkCredential cred)
{
    try
    {
        FtpWebRequest req = (FtpWebRequest)WebRequest.Create(dirUrl + "/");
        req.Method = WebRequestMethods.Ftp.ListDirectory;
        req.Credentials = cred;
        using (req.GetResponse()) { }
        return true;
    }
    catch (WebException)
    {
        return false;
    }
}

注意DirectoryExists里dirUrl后面要加,否则部分服务器会当成文件来查。

同名文件覆盖还是跳过

这取决于业务需求,没有绝对答案,C#里控制覆盖只需一个FtpWebRequest属性:request.Method = UploadFile本身就是覆盖上传,服务器默认会替换同名文件,如果你想保留远端旧文件,得先检查是否存在:

// 检查远程文件是否存在
private static bool RemoteFileExists(string fileUrl, NetworkCredential cred)
{
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(fileUrl);
    request.Method = WebRequestMethods.Ftp.GetFileSize;
    request.Credentials = cred;
    try
    {
        using (request.GetResponse()) { return true; }
    }
    catch (WebException ex)
    {
        FtpWebResponse response = (FtpWebResponse)ex.Response;
        if (response != null && response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
            return false;
        throw;
    }
}

行业共识认为,判断文件是否存在的最可靠方式是发送GetFileSize请求,返回成功说明文件在,返回550错误说明不存在,这个方法比ListDirectory再解析文件名更省事。

断点续传与进度显示

传了一半断网,重新传整个文件太亏,FTP协议支持AppendFile模式,从文件末尾继续写,可惜FtpWebRequest对Append的支持有些坑,.NET Framework里可以用request.Method = WebRequestMethods.Ftp.AppendFile,但.NET Core/5+里这个方法有兼容性问题,会抛出ProtocolError。

可靠的做法是手动实现续传逻辑:先查远程文件大小,从那个偏移量开始读本地文件,然后设置请求头的ContentOffset。

带进度回调的断点续传实现

public static void UploadWithResume(string localPath, string remoteUrl, NetworkCredential cred, Action<long, long> progressCallback)
{
    long existingSize = GetRemoteFileSize(remoteUrl, cred);
    long offset = existingSize > 0 ? existingSize : 0;
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(remoteUrl);
    request.Method = WebRequestMethods.Ftp.UploadFile; // 注意:还是UploadFile
    request.Credentials = cred;
    request.UseBinary = true;
    request.UsePassive = true;
    request.ContentOffset = offset; // 关键:从偏移量开始写
    using (FileStream fs = File.OpenRead(localPath))
    {
        if (offset >= fs.Length)
        {
            Console.WriteLine("文件已存在且大小一致,无需上传");
            return;
        }
        fs.Seek(offset, SeekOrigin.Begin);
        long totalBytes = fs.Length;
        byte[] buffer = new byte[8192];
        int bytesRead;
        long sentBytes = offset;
        using (Stream reqStream = request.GetRequestStream())
        {
            while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
            {
                reqStream.Write(buffer, 0, bytesRead);
                sentBytes += bytesRead;
                progressCallback?.Invoke(sentBytes, totalBytes);
            }
        }
    }
    using (var resp = (FtpWebResponse)request.GetResponse())
    {
        Console.WriteLine(resp.StatusDescription);
    }
}

ContentOffset这个属性是FtpWebRequest专门用来续传的,客户端告诉服务器“我这次只传后半段”,服务器会从指定位置接着写,不过要注意,部分FTP服务器软件(比如早期Serv-U)不支持这个特性,会直接报错,遇到这种情况,要么换WebClient配合自定义协议,要么放弃续传直接全量覆盖。

进度回调的好处是你可以接一个进度条,progressCallback里收到的是已传字节和总字节,算百分比就完事了。

异步上传与超时重试机制

UI线程阻塞是WinForm和WPF里常犯的错,上传大文件时界面卡死,用户以为程序崩溃了,用async/await配合Task.Run或者直接调用异步方法能解决。

基于FtpWebRequest的异步方法封装

FtpWebRequest本身没有内置异步方法,但可以借助Task.Factory.FromAsync包装,或者直接放到线程池里跑,更简洁的做法是用WebClient的UploadFileAsync,它支持异步和进度事件:

using (WebClient client = new WebClient())
{
    client.Credentials = cred;
    client.UploadProgressChanged += (s, e) =>
    {
        Console.WriteLine($"已上传 {e.BytesSent} / {e.TotalBytesToSend}");
    };
    await client.UploadFileTaskAsync(remoteUrl, localPath);
}

UploadFileTaskAsync是.NET 4.5以上提供的异步方法,返回Task,配合await使用,UI线程不阻塞,缺点是没有ContentOffset,不能断点续传,所以小文件优先用WebClient,大文件用FtpWebRequest手动封装。

上传失败重试策略

网络抖动是家常便饭,直接失败重传最稳妥,简单的重试循环:

public static async Task UploadWithRetryAsync(string localPath, string remoteUrl, NetworkCredential cred, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            await UploadFileTaskAsync(localPath, remoteUrl, cred);
            return;
        }
        catch (Exception ex) when (attempt < maxRetries)
        {
            Console.WriteLine($"第{attempt}次上传失败:{ex.Message},2秒后重试");
            await Task.Delay(2000);
        }
    }
}

这里UploadFileTaskAsync是自己写的异步封装,可以用上面的UploadWithResume改造,也可以直接用UploadFileStream的异步版本,重试间隔别太短,2秒是比较中庸的选择。

推荐第三方库:WinSCP .NET与FluentFTP

如果你觉得FtpWebRequest太底层,处理不了SFTP、加密、复杂目录树,那就不用硬扛,社区里有两个成熟方案:

库名 协议支持 断点续传 异步支持 适用场景
FluentFTP FTP, FTPS 纯FTP场景,API简洁
WinSCP .NET FTP, SFTP, FTPS 需要SFTP,或想用WinSCP GUI逻辑

FluentFTP用法就像写普通代码:

using (var conn = new FtpClient("192.168.1.100", "user", "pass"))
{
    conn.Connect();
    conn.UploadFile(localPath, remotePath, FtpRemoteExists.Overwrite, true, new Progress<double>(p => Console.WriteLine(p)));
}

它内部处理了目录创建、重试、编码等琐碎问题,适合快速交付,如果你问c#怎么将文件上传到ftp服务器最省心,直接引入FluentFTP就是答案。

WinSCP .NET更适合对可靠性要求极高的场景,它封装的是WinSCP命令行逻辑,支持断点续传、同步、权限设置,而且背后的WinSCP有二十年历史,稳定性经过大量验证。

常见问题排查

上传报错很大一部分出在路径和权限上,下面列几个高发问题:

异常信息 原因 解决方法
(550) File unavailable 远程目录不存在 先调用EnsureRemoteDirectory
(530) Not logged in 账号密码错误,或IP被限制 检查凭据,确认FTP账号是否只能从特定IP登录
(425) Can't open data connection 被动模式被服务器防火墙拦截 切换UsePassive = false试试
(500) Invalid command 服务器不支持当前FTP命令 换WebRequestMethods.Ftp里的其他枚举,或改用SFTP
System.Net.Sockets.SocketException 服务器域名解析失败或端口不通 用telnet ip 21测试端口连通性

补充一条:公司内网里的FTP服务器,如果绑定的是活动目录用户,密码就不能随便改,改之前要通知IT。

从本地上传的完整示例代码

把前面零散的逻辑拼成一个可直接运行的类,包含目录创建、上传、重试:

public class FtpUploader
{
    private readonly string _host;
    private readonly NetworkCredential _cred;
    public FtpUploader(string host, string user, string pass)
    {
        _host = host.TrimEnd('/') + "/";
        _cred = new NetworkCredential(user, pass);
    }
    public void Upload(string localPath, string remoteSubPath)
    {
        string fullRemotePath = _host + remoteSubPath;
        string remoteDir = fullRemotePath.Substring(0, fullRemotePath.LastIndexOf('/'));
        EnsureRemoteDirectory(remoteDir);
        UploadFileStream(localPath, fullRemotePath);
    }
    // 具体方法实现参考前文...
}

这段代码可以直接贴进项目里改改路径就能用,或者结合你自己的日志模块把上传状态记下来。

掌握了FtpWebRequest就等于掌握了C# FTP上传的地基,再配合FluentFTP这类工具,应对日常文件同步绰绰有余,别忘了上传前检查目录、大文件用流式读写、关键时刻断点续传,这三点做好了,你的FTP功能就能稳定跑很久。

关于C# FTP上传的常见问题

C#怎么将文件上传到FTP服务器并自动创建子目录?

先按拆分远程路径,逐级调用MakeDirectory创建不存在的目录,再执行上传,可以复用前文EnsureRemoteDirectory方法,注意每次创建后要释放响应流,否则连接数会涨。

FTP上传大文件时进度条卡住不动,怎么回事?

多半是同步上传阻塞了UI线程,改用UploadFileTaskAsync或者把上传逻辑放进Task.Run,同时在UploadProgressChanged事件里更新进度条控件,界面就会流畅更新。

FluentFTP和FtpWebRequest哪个更适合做批量上传?

批量上传涉及遍历本地目录、多线程并发和错误重试,FluentFTP提供了现成的UploadDirectory方法,能递归上传整个文件夹,FtpWebRequest则要自己写循环和异常处理,开发效率要求高就选FluentFTP,想减少依赖就自己写底层。

首发原创文章,作者:王坚‌,如若转载,请注明出处:https://idctop.com/article/785497.html

赞 (0)
上一篇 2026年11月6日 17:38
下一篇 2026年11月6日 17:39

相关推荐

  • 我的世界服务器不让装模组如何是好,为什么装不上?

    服务器不让装mod,最直接的办法是分清“服务端mod”和“客户端mod”——服务端装不了就转投纯客户端mod,这类mod在本地生效,不需要服务器授权,也不违反服务器规则,很多玩家一听到“服务器不让装mod”,第一反应是放弃,或者去搜各种绕过检测的偏方,在《我的世界》里,“不让装mod”这件事得拆开看,是服务器完……

    2026年9月10日
    200
  • 电子邮箱中map服务器怎么填,imap和smtp怎么设置

    邮箱服务器地址通常由你的邮箱服务商决定,以“.com”或“.cn”结尾的域名就是最直接的线索;如果你不确定,最稳妥的办法是登录网页版邮箱,在“设置”或“帮助中心”里查看官方给出的POP3、IMAP、SMTP服务器名,先搞清楚:你问的“map”到底指什么?很多人在填邮箱配置时,会把“IMAP”误打误撞成“map……

    2026年10月4日
    000
  • 服务器如何限制一个IP多个访问权限,怎么设置IP访问限制?

    服务器限制一个IP多个访问权限,本质是从Web层、系统内核层、防火墙层、硬件层四个维度设置并发连接数、访问频率和带宽阈值,且必须结合业务场景分层施策,才能既阻断恶意高频访问,又保证正常用户不受误伤,什么场景下必须限制单个IP的访问量一台服务器收到来自同一IP的大量请求,不见得都是攻击,但无论出于什么原因,超出业……

    2026年10月6日
    200
  • DNS服务器故障怎么办,是什么原因导致的?

    DNS服务器故障别慌,按重启网络设备、改公共DNS、清本地缓存这三步走,九成情况能自己修好,判断标准很简单,微信能收到消息但网页打不开,多半就是DNS在闹脾气,dns服务器故障怎么修复?先确认问题出在哪个环节分清”全挂”还是”个别网站打不开”先做个快速测试,打开浏览器,故意输入一个平时不常访问的网站,比如某个冷……

    2026年10月2日
    200
  • 用友T3装好后怎么连接服务器,连接不上怎么办?

    T用友T3装好后连接不上服务器,核心操作是先在服务器端开启SQL Server的TCP/IP协议并放行1433端口,然后在客户端的hosts文件中绑定服务器IP和计算机名,最后用系统管理以admin身份重新登录,装好用友T3先分清服务器端和客户端很多人卡在连接服务器这一步,是因为压根没搞清楚自己装的是哪一端,用……

    程序编程 2026年10月22日
    100
  • 戴尔T430服务器怎么做RAID1,具体步骤是什么?

    戴尔T430服务器RAID1配置步骤?先记住一句话戴尔T430服务器做RAID1,核心就两步:在PERC阵列卡里把两块硬盘组成虚拟磁盘,然后在装系统时加载阵列驱动, 这块机器在国内中小机房、企业自建机房里很常见,不少朋友买来是为了跑数据库或文件共享,数据安全是第一位,所以RAID1这个镜像模式是最稳妥的选择,戴……

    2026年9月21日
    300
  • 如何实现多线程?ASP.NET多线程高效并发处理指南

    ASP.NET 多线程ASP.NET 多线程编程是构建高性能、高响应性Web应用的核心技术,它允许应用程序同时执行多个任务,充分利用现代多核处理器的计算能力,有效提升吞吐量,处理密集型操作时保持UI响应,并优化后台任务执行效率,掌握其原理与最佳实践对开发高效服务至关重要, ASP.NET 多线程基础与环境ASP……

    2026年2月12日
    13700
  • 服务器AI管理平台怎么选?服务器AI管理平台哪个好

    高效、智能、可扩展——现代数据中心亟需统一的服务器AI管理平台在云原生与AI算力爆发式增长的背景下,传统服务器运维模式已难以应对复杂性、异构性与实时性挑战,服务器AI管理平台正成为数据中心智能化升级的核心基础设施——它通过AI驱动的自动化、预测性维护与资源智能调度,将服务器管理效率提升40%以上,故障响应速度缩……

    程序编程 2026年4月16日
    5600
  • QQ邮箱收发服务器怎么填,pop3和smtp怎么设置?

    QQ邮箱接收和发送服务器填写的核心答案是:接收服务器(POP3)填pop.qq.com,发送服务器(SMTP)填smtp.qq.com,若使用IMAP协议则接收服务器填imap.qq.com,端口号分别对应995、465和993,且均需开启SSL加密,这是几乎所有邮件客户端配置QQ邮箱的通用标准,无论是电脑端O……

    程序编程 2026年10月31日
    000
  • win7服务器断电后开不了机怎么办,无法开机是什么原因

    win7服务器断电后无法开机怎么办——先别急着重装系统win7服务器断电后无法开机怎么办?多数情况下,这不是硬件报废,而是电源保护机制触发或系统引导文件损坏,先做硬件排查,再进修复模式,最后才考虑重装,第一步:判断故障类型,别让服务器“带病工作”断电对服务器的伤害分三个层面:电源、硬件、系统,开机时留意三个信号……

    2026年8月26日
    800

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注