我只想使用C#从FTP服务器的多个线程中的多个段中下载单个文件。
是否可以像HttpWebRequest
中那样提供文件下载范围?
首先免责声明: 多任务处理不是不是神奇的“更快”子弹。如果将其应用于错误的问题,则最终代码会变得比普通的单任务/顺序方法更复杂/更容易出错,对内存的要求更高,并且实际上更慢。通常,一项长期运行的替代任务是强制性的。但是大规模并行化仅在非常特殊的情况下进行。
常规文件操作受磁盘或网络限制。多任务处理不会对磁盘或网络绑定的操作增加任何加速。确实可能会导致速度降低,因为NCQ和类似功能必须理顺您的随机访问请求。有了Netowrking,有时 会有所帮助。 某些服务器确实设置了“每个连接”限制,因此可以通过单独的下载将下载分成多个段,从而可以加快网络速度。 但是可以肯定的是,这里确实如此。考虑除Speed Rant的第1点之外的所有内容。
假设FTPWebRequest仍然是您正在使用的类,则看起来ContentLenght和ContentOffset可能是您正在寻找的机器人。基本上,它的使用类似于子字符串-每个连接/子请求都从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,read)) > 0))
{
fileStream.Write(buffer,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();