Reputation: 103
I just wanted to download a single file from FTP server in multiple segments in multiple threads using C#.
Is it possible to give ranges for the file download like in HttpWebRequest
?
Upvotes: 3
Views: 2069
Reputation: 202292
You can use FtpWebRequest.ContentOffset
to specify the starting offset.
But FtpWebRequest.ContentLength
is not implemented. To workaround that you have to abort the download, once you receive the desired amount of bytes.
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();
Upvotes: 0
Reputation: 1
The default connection limit is 2. Use ServicePoint
to change the limit.
request.Method = WebRequestMethods.Ftp.DownloadFile;
ServicePoint sp = request.ServicePoint;
sp.ConnectionLimit = 4;
Upvotes: 0
Reputation: 9804
First the disclaimer: Multitasking is not a magical "go faster" bullet. If you apply it to the wrong problem, you end up with code that is more complex/prone to errors, more memory demanding and actually slower then the plain old singletasked/sequential approach. One alternate Task for a long running operation is genereally mandatory. But Massive parallelisation is only in very specific circumtances.
Generaly file operations are Disk or Network bound. Multitasking will not add any speedup to Disk or Network bound operations. And indeed might cause a slowdown, as NCQ and similar features have to straightern out your random access requests. That being said with Netowrking it sometimes can help. Some servers do apply a "per connection" limit, and thus splitting the download into multiple segments with their own connection can be a net speedup by. But be certain this is actually the case here. Consider all but point 1 of the Speed Rant.
Asuming FTPWebRequest is still the class you are using, it looks like ContentLenght and ContentOffset might be the droids you are looking for. You basically use it similar to substring - each connection/sub-request takes X bytes from Y offset.
Upvotes: 1