Code Sample: Windows 8.1 Sockets: Ftp Client Sample with Sockets in C#/Xaml
In my first post, I tried sharing my experience with the socket connection to an ftp server and executing different commands using the simple text commands.
Here, I will try to explain the implementation of the read and writing into a socket stream on the ftp and building higher level functions that deal with StorageFile. Using the StorageFile abstraction, we can easily make the ftp implementation available for contracts and extensions like File Save Picker, Share Contract, File Open Picker. This would make your application even more “immersive” and give it a more native look.
Passive Mode
To execute uploads and downloads, it is a better idea to use a passive channel. Passive mode works almost like a Duplex channel in WCF. We use the main channel to keep the communication up and running with the ftp server and the passive channel to retrieve data from the FTP server.
The procedure goes as follows:
1) We send a request declaring that we want to open a passive channel (PASV command)
2) The server responds with the a success response and the endpoint and port to connect to
protected virtual void ParseMessage() { var matches = Regex.Match(Message, @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)" ); if (!matches.Success || matches.Groups.Count != 7) throw new FtpException(string.Format("Malformed PASV response: {0}", Message)); m_HostName = new HostName(string.Format("{0}.{1}.{2}.{3}", matches.Groups["quad1"].Value, matches.Groups["quad2"].Value, matches.Groups["quad3"].Value, matches.Groups["quad4"].Value)); m_ServiceName = ((int.Parse(matches.Groups["port1"].Value) << 8) + int.Parse(matches.Groups["port2"].Value)).ToString(); }
3) We open a secondary channel and start listening while executing the command on the main channel.
There are different passive modes and connection types but this is not main scope of this posting.
Using the steps defined above we can write a higher method that should return the response from the secondary channel.
public async Task<T> ExecuteInDuplexAsync<T>(FtpDataConnectionType connectionType, FtpRequest request) where T : FtpDuplexReponse { var response = Activator.CreateInstance<T>(); response.Request = request; if (!IsConnected) throw new FtpException("Not Connected"); var passiveChannel = await OpenPassiveDataStreamAsync(connectionType, request); string bufferResult; while ((bufferResult = await passiveChannel.ReadLineAsync(Encoding)) != null) { if (bufferResult.Length > 0) { response.MessageBody += bufferResult; System.Diagnostics.Debug.WriteLine(bufferResult); } } var finalResponse = await GetReplyAsync<FtpResponse>(); response.Code = finalResponse.Code; response.InfoMessages += finalResponse.Message; return response; }
Directory Listing
To get the directory listing, the command to execute is LIST. Considering the possible amount of data that we might need to retrieve, we are again going to be using a passive channel to execute this command.
NOTE: The response to this command might be in several different formats (e.g. UNIX, VAX, DOS etc.). Since the data we are retrieving is in flat text format, the only way to parse the data is to use different parser for each different type.
The directory listing operation implementation looks something like:
public async Task<FtpGetListingResponse> GetListingAsync(string path = null) { if (path == null || path.Trim().Length == 0) path = (await GetDirectoryAsync()).Directory; // always get the file listing in binary // to avoid any potential character translation // problems that would happen if in ASCII. await SetDataTypeAsync(FtpDataType.Binary); return await ExecuteInDuplexAsync<FtpGetListingResponse>(FtpDataConnectionType.AutoPassive, new FtpListRequest(path)); }
As one can see, we are delegating the parsing of the response to the response message.
Retrieving an Ftp File
Once the ftp connection is set up and we get the listing of files, we are ready to retrieve a file from the server using a passive connection. For the retrieval operation we are going to use a RETR command. We first move to PASV mode and execute the RETR command to receive the stream on the passive channel.
if (!(reply = await SetPassiveMode()).Success) throw new FtpCommandException(reply); var passiveConnection = new FtpStreamChannel(); await passiveConnection.ConnectAsync(reply.HostName, reply.ServiceName); if (!(await ExecuteAsync<FtpResponse>(request)).Success) { passiveConnection.Dispose(); throw new FtpCommandException(reply); }
Once we have the passive channel open and ready to read, the rest of the implementation is a simple network stream read.
A nice windows store apps implementation here would be to use a streamed storage file reference. By this way, the higher stacks of the application would realize the file almost like a local file.
StorageFile resultantFile; // // A more efficient way, maybe a DataReader can be used here using (var stream = await OpenReadAsync(path)) { var buffer = new byte[512].AsBuffer(); var resultingBuffer = new byte[0]; while (true) { IBuffer readBuffer = await stream.ReadAsync(buffer, 512, InputStreamOptions.Partial); if (readBuffer.Length == 0) break; resultingBuffer = resultingBuffer.Concat(readBuffer.ToArray()).ToArray(); } resultantFile = await StorageFile.CreateStreamedFileAsync(path.GetFtpFileName(), async (fileStream) => { await fileStream.WriteAsync(resultingBuffer.AsBuffer()); fileStream.FlushAsync(); fileStream.Dispose(); }, null); }
So now that we have the directory listing and the Storage file functions, it is not so hard to implement a File Open Picker extension in our application and let other windows store applications to use the registered FTP Server as the file source for their processes.
Further Implementation
In this example, I am using a 512 buffer to download the file without using any progress callbacks or Cancellation tokens. The implementation of an IAsyncOperationWithProgress return type would greatly increase the usability of this function. It would actually give the user a chance to cancel the long running operation; not letting your application to hang.
Uploading an FTP File
The upload file feature is quite straightforward considering the explanation above, using a passive channel and using the STOR command. We can use the same open passive channel stream command we implemented for retrieval; execute PASV, get the reply channel connection parameters and use this channel to upload the file.
The only main difference between the retrieve and store commands is the fact that for the store feature, ftp server does not respond with an ok reply. The server assumes that as soon as the client closes the passive channel the upload operation is completed. (i.e. we do not need to call the GetReply function after the upload is completed)
using (var stream = await OpenWriteAsync(destination)) { await stream.WriteAsync(buffer.AsBuffer()); await stream.FlushAsync(); }
In order to keep the interface nice and “immersive” we can accept a storage file as a parameter and use the file data to create the ftp file.
public async Task UploadFileAsync(StorageFile file, string destination) { using (var stream = await OpenWriteAsync(destination)) { // // A more efficient way, maybe a DataReader can be used here using (var readStream = await file.OpenReadAsync()) { var buffer = new byte[512].AsBuffer(); var resultingBuffer = new byte[0]; while (true) { IBuffer readBuffer = await readStream.ReadAsync(buffer, 512, InputStreamOptions.Partial); if (readBuffer.Length == 0) break; resultingBuffer = resultingBuffer.Concat(readBuffer.ToArray()).ToArray(); } await stream.WriteAsync(resultingBuffer.AsBuffer()); await stream.FlushAsync(); } } }
Further Implementation
Again here we can use a more agile implementation, making use of a progress callback and a cancellation token.
So there we have it; we have two methods in our ftp client that we can easily adapt to use in a windows store application.
- public async Task UploadFileAsync(StorageFile file, string destination)
- public async Task RetrieveFileAsync(string path)
Hope this helps someone out there who is trying to include an FTP server in their Windows Store App.
Happy coding everyone.
Pingback: FTP Endeavor II – Upload and Download Files
Good job! I searched so long for a sample to upload files on a FTP Server in Windows Store Apps. Now I found a nice tutorial 🙂
LikeLike
thanks mate… glad it helped… (the gallery sample might be missing the last method implementation but still the whole structure is there)
LikeLike
Hello Can,
Thank you so much for the samples, appreciate it a lot.
I’m having a problem, though. When I upload a file, the server seems like keeps waiting for more data to come after the file has been uploaded. After this, the server doesn’t accept more commands and the file can’t be accessed until I close the app.
It’s important to note that after closing the app I can access the file and it has been perfectly uploaded.
My understanding is that when the data stream is disposed the server should assume the operation is completed, but it doesn’t look like it. Note that other commands can be issued without problem, like listing or creating a directory.
Any ideas?
Thank you so much again!
LikeLike
your situation seems to be similar to what Eduard is receiving below. I am going to be uploading a universal app sample in the coming days, I will try to replicate the error.
LikeLike
Thank you very much for this examples!
Unfortunately I get a “System.ObjectDisposedException” on uploading a mp4 from WP 8.1 IsoStorage.
Finally I have a 0 Byte file on the server. Do you have an idea for the reason?
LikeLike
hey, no problems… I really have no idea what is going on… which type of ftp server were you using for this scenario? I will uploading a universal app sample in the coming days… will try to replicate the issue during testing…
LikeLike
I used my web hoster ftp. But also the xampp ftp. I think the problem is the method you are using to read and write the file. This is a very hard topic for me so can’t give you more information. And you should use a larger file for testing. > 5MB.
LikeLike
Porting to Windows 10 for a windows 10 app (app requires a specific windows 10’s feature).
I’m getting a ‘System.Runtime.InteropServices.COMException’ in mscorlib.ni.dll exception
“WinRT information: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.”
in FtpStreamChannel’s method public async Task ConnectAsync(HostName host, string service)
after calling await ftpClient.GetListingAsync() in my app.
So far GetListingAsync() , FileExistsAsync(), OpenWriteAsync() all fires the System.Runtime.InteropServices.COMException
This seems to happen after any method that sets mode to EPSV
EXEC :EPSV
REPLY :229 Entering Extended Passive Mode (|||5093|)
thread exits messages, then error. I know EPSV is established based on the reply.
I know MS changed the way they did sockets for 8.1/10, mainly that they didn’t include a method to close the socket when your done with it, but instead rely on garbage collection. There seems to be issues with the WinRT kernel when knowing when that will be done. StackExchange mentions deleting the pointer in C++ but this of course is C#.
tested connection with WinSCP, seems to work fine. non loopback, external ftp. Turned off firewall, same issue.
Making directory, connecting, all seem to be fine.
Any suggestions?
LikeLike
found my own issue here.. seems to be if the ftp server doesnt provide root folder access, need to specify the path… or things fall apart.
still couldnt get the file to transfer… some reason hangs on a handle to the file and 0B until forced closed.
LikeLike
Thank you very much for your example! It helped me a lot to get started. Uploading a single file is working very good, but when I make a loop with multiple Files, it is not working. It looks like the Stream for writing on the FTP Server is not correctly flushed or closed. An ugly fix right now is the Disconnect after the first File was uploaded and then reconnect in the loop. Is there something missing? Like a command for the FTP that the STOR is complete?
LikeLike
Hey hi
How can i upload a large file let say ->> 500 or 700 mb over FTP, at a great speed.
I am targeting a speed of 20mb/s, i have successfully transferred the FILE data to FTP at a speed of 1mb/s through my mobile device (UWP application).
Can you help me in any case with the uploading of FILE at a much faster speed.
-> Should i be altering the Byte Array size or anything.
Thanks and Regards in advance
Devanshu
LikeLike
Hi
I am targeting a FTP upload of a large file say 700mb at a great speed say 30-40mb/s from my mobile device i.e. UWP application.
I have successfully transferred the File to FTP but at a lower speed viz. 1mb/s
Can you help me and comment on how can i do a faster FTP upload from Windows 10 device.
Thanks
Devanshu
LikeLike