使用 C# 在多线程中从 FTP 服务器分块下载单个文件

Download single file from FTP server in chunks in multiple threads using C#

我只想使用 C# 从 FTP 服务器的多个线程中的多个段下载单个文件。

是否可以像 HttpWebRequest 那样给出文件下载的范围?

首先是免责声明: 多任务处理不是神奇的"go faster"子弹。如果你将它应用到错误的问题上,你最终会得到更多 complex/prone 错误的代码,更多的内存需求,实际上 比旧的 singletasked/sequential 慢 方法。一个长 运行 操作的替代任务通常是强制性的。但是大规模并行化只是在非常特殊的情况下。

一般文件操作受限于磁盘或网络。多任务处理不会为磁盘或网络绑定操作增加任何加速。确实可能会导致速度变慢,因为 NCQ 和类似的功能必须理顺您的随机访问请求。与 Netowrking 相比,有时 会有所帮助。 一些 服务器确实应用了 "per connection" 限制,因此将下载分成多个具有自己的连接的段可以是网络加速。 但可以肯定的是,这里确实是这种情况。考虑 Speed Rant.

的第 1 点以外的所有内容

假设 FTPWebRequest is still the class you are using, it looks like ContentLenght and ContentOffset 可能是您正在寻找的机器人。您基本上使用它类似于子字符串 - 每个 connection/sub-request 从 Y 偏移量中获取 X 个字节。

您可以使用FtpWebRequest.ContentOffset指定起始偏移量。

但是FtpWebRequest.ContentLength没有实现。要解决您必须在收到所需字节数后中止下载的问题。

const string name = "bigfile.dat";
const int chunks = 3;
const string url = "ftp://example.com/remote/path/" + name;
NetworkCredential credentials = new NetworkCredential("username", "password");

Console.WriteLine("Starting...");

FtpWebRequest sizeRequest = (FtpWebRequest)WebRequest.Create(url);
sizeRequest.Credentials = credentials;
sizeRequest.Method = WebRequestMethods.Ftp.GetFileSize;
long size = sizeRequest.GetResponse().ContentLength;
Console.WriteLine($"File has {size} bytes");
long chunkLength = size / chunks;

List<Task> tasks = new List<Task>();

for (int chunk = 0; chunk < chunks; chunk++)
{
    int i = chunk;
    tasks.Add(Task.Run(() =>
        {
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
            request.Credentials = credentials;
            request.Method = WebRequestMethods.Ftp.DownloadFile;
            request.ContentOffset = chunkLength * i;
            long toread =
                (i < chunks - 1) ? chunkLength : size - request.ContentOffset;
            Console.WriteLine(
                $"Downloading chunk {i + 1}/{chunks} with {toread} bytes ...");

            using (Stream ftpStream = request.GetResponse().GetResponseStream())
            using (Stream fileStream = File.Create(name + "." + i))
            {
                byte[] buffer = new byte[10240];
                int read;
                while (((read = (int)Math.Min(buffer.Length, toread)) > 0) &&
                       ((read = ftpStream.Read(buffer, 0, read)) > 0))
                {
                    fileStream.Write(buffer, 0, read);
                    toread -= read;
                }
            }
            Console.WriteLine($"Downloaded chunk {i + 1}/{chunks}");
        }));
}

Console.WriteLine("Started all chunks downloads, waiting for them to complete...");
Task.WaitAll(tasks.ToArray());

Console.WriteLine("Done");
Console.ReadKey();