使用 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();
我只想使用 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();