FTP client begins by settings up a socket to the FTP server, authorize itself and then continues by sending instructions. All communication is done in plain text which makes the protocol both easy to understand and use.
To save you the time, the completed client and channel are at Windows 8.1 Sockets: Ftp Client Sample with Sockets in C#/Xaml
Many FTP clients were written in .NET used active and passive sockets to connect to ftp servers. Most of these are open source and easily accessible for .NET developers.
However, in Windows Runtime, things have been revised for sockets namespace and the only example I could find that interacts with an FTP server was actually using web requests to execute commands. While it actually provides a viable solution, I couldn’t use concepts like progress, cancellation, passive connections etc. So that’s how the ftp endeavor started.
Setting-up Development Environment
Instead of using one of the company servers, I wanted use a local ftp server for development and testing. So I found the open source solution FileZilla to run an ftp server locally and follow the execution of commands.
However, in my initial attempts to open a socket connection to my local server, I had some issues with remote (not really remote in this case) host not being found. I could connect to my home media server without problems, but each time I tried to open a connection to ftp://127.0.0.1, it was throwing and exception.
“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. (Exception from HRESULT: 0x8007274C)”
First thing I checked was the application capabilities. (i.e. Package Manifest). Even though I was using a test project, you have to set the proper capabilities in the unit test project.
However, this was not the issue; the capabilities were set correctly and I still could not connect to the localhost.
As it turns out, the problem was the loopback check that is part of the Network Isolation that is applied to the Windows Store Application. (Following the steps from Troubleshooting and debugging network connections (Windows Store apps) ). So basically, unless the local connection is to the application itself, the connection is blocked by the network isolation loopback check.
At this point, I remembered when I was installing Fiddler (i.e. for people who are not familiar, this is a network monitor/sniffer that sets up a local proxy to your applications to follow the request/responses.), it was asking if I wanted to disable the loopback check for my applications. Using the inbuilt Enable Loopback Utility I was able to add my test project to exemptions list and continue the implementation.
FTP Socket Connection
Setting up the FTP socket connection was pretty straight forward. We simply use a StreamSocket to establish the connection. All we need is the host name and use “21” as the service name. Since my local server did not require any secure connection, I am also going to be using a plain socket.
await m_StreamSocket.ConnectAsync(host, service, SocketProtectionLevel.PlainSocket);
Here if there is a problem with the socket connection, you can use the SocketError utility class to get the socket error status for error handling. I am just going to be writing the error to the debug console.
catch (Exception ex) { var socketError = SocketError.GetStatus(ex.HResult); System.Diagnostics.Debug.WriteLine("There was a connection problem: {0}", ex.Message); System.Diagnostics.Debug.WriteLine("Socket Error: {0}", socketError); return false; }
Executing Commands
Once the connection is set up, we can start executing commands on this stream socket in plain text. For executing the command we just need a method that will send the plain text with the proper encoding.
/// <summary> /// Writes a line to the stream using the specified encoding /// </summary> /// <param name="encoding">Encoding used for writing the line</param> /// <param name="buf">The data to write</param> public IAsyncOperationWithProgress<uint, uint> WriteLineAsync(Encoding encoding, string buf) { byte[] data = encoding.GetBytes(string.Format("{0}\r\n", buf)); return WriteAsync(data.AsBuffer()); }
And execute method would look something like:
/// <summary> /// Executes a request on the ftp server /// </summary> /// <typeparam name="T">Type of the expected response</typeparam> /// <param name="request">Request to execute</param> /// <returns></returns> public async Task<T> ExecuteAsync<T>(FtpRequest request) where T:FtpResponse { if(!IsConnected) throw new FtpException("Not Connected"); await WriteLineAsync(Encoding, request.Command); await FlushAsync(); return await GetReplyAsync<T>(); }
Here I am using an FtpRequest base class to create the commands that I want to send to the FTP server. The reason for this is to strongly type different commands with appropriate parameters.
public class FtpRequest { public string CommandName { get; set; } public string[] Arguments { get; set; } public string Command { get { if (Arguments != null && Arguments.Length > 0) return string.Format("{0} {1}", CommandName, Arguments.Aggregate((item1,item2)=>item1 + " " + item2)); return CommandName; } } public FtpRequest() { } public FtpRequest(string commandName, params string[] arguments) { CommandName = commandName; if (arguments != null) Arguments = arguments; } }
You notice that the execute command function immediately calls the GetReply method. This method reads the response from the FTP server (e.g. in the initial connection, you would receive the 220 status code and the welcome message).
For the GetReply method, all we need is a ReadLine statement, and a way to parse the status code that is sent and the additional messages from the FTP server.
/// <summary> /// Retrieves a reply from the server. Do not execute this method /// unless you are sure that a reply has been sent, i.e., you /// executed a command. Doing so will cause the code to hang /// indefinitely waiting for a server reply that is never coming. /// </summary> /// <returns>FtpReply representing the response from the server</returns> internal async Task<T> GetReplyAsync<T>() where T:FtpResponse { var reply = Activator.CreateInstance<T>(); if (!IsConnected) throw new InvalidOperationException("No connection to the server has been established."); string buf; while ((buf = await ReadLineAsync(Encoding)) != null) { Match m; if ((m = Regex.Match(buf, "^(?<code>[0-9]{3}) (?<message>.*)$")).Success) { reply.Code = m.Groups["code"].Value; reply.Message = m.Groups["message"].Value; break; } reply.InfoMessages += string.Format("{0}\n", buf); } return reply; }
With the implementation of this method, we prepared the foundation for the basic command calls to the FTP server. (i.e. commands that do not require a duplex or passive channel such as USER, PASS, CWD, PWD… etc.)
Authenticating with the FTP Server
For authenticating with the server, we would need to send two commands. One is the USER and the second one is PASS. (i.e. command name and one argument for the value)
For instance the USER request would look something like:
public class FtpUserRequest : FtpRequest { public FtpUserRequest(string userName) : base("USER", userName) { } }
And we can send this request using the Execute method we previously implemented:
public Task<FtpResponse> SetUserAsync(FtpUserRequest request) { return ExecuteAsync<FtpResponse>(request); }
After connecting to the ftp server and executing the user and pass requests, the requests and responses would look something like:
220-FileZilla Server version 0.9.43 beta
220-written by Tim Kosse (tim.kosse@filezilla-project.org)
220 Please visit http://sourceforge.net/projects/filezilla/
EXEC :USER can
REPLY :331 Password required for can
EXEC :PASS
REPLY :230 Logged on
And my ftp server shows the console output as:
Hope this post was helpful to those of you who are trying to integrate an FTP server in their windows store applications.
In the next posts I will try to talk about my experience with more complex commands and the passive channels.
Happy coding everyone!
Pingback: FTP Endeavor II – Upload and Download Files | Can Bilgin
Hi , maybe someone could help me. I add the dll to my project and I tried to download(read) one file from ftp. You can see a part of my code,
(…)
var ftpClient = CreateFtpClient();
await ftpClient.ConnectAsync();
if (!ftpClient.IsConnected)
{
System.Diagnostics.Debug.WriteLine(“No connection could be made to the FTP server {0}:{1}”, m_Host, “21”);
}
else
{
string workDir = await ftpClient.GetWorkingDirectoryAsync();
var fileFromFtp = await ftpClient.RetrieveFileAsync(“/” + fileFullPath);
}
(…)
My application connect to the server with no problem, i can list all files, but when I try to read a file I am getting error that the object has been close. Errors below. Could anyone tell me what I am doing wrong (I would like to use storagefile)
Error:
A first chance exception of type ‘System.ObjectDisposedException’ occurred in mscorlib.ni.dll
The thread 0xff0 has exited with code 259 (0x103).
The thread 0xfe8 has exited with code 259 (0x103).
The thread 0xf90 has exited with code 0 (0x0).
The thread 0xf98 has exited with code 0 (0x0).
The object has been closed. (Exception from HRESULT: 0x80000013)
Thanks a lot!
LikeLike
i ended up on the same problem. somewhen the server connection runs into a timeout or something like that. but even keepalive did not improve it. Did you get a solution?
LikeLike
Thanks for your work. It helped me a lot
LikeLike
does it also run on win10 uwp?
LikeLike
that is a good question… I will try to put together a sample for UWP… I don’t see a reason why not… also am planning to create a nuget package for this library…will announce here as soon as ready.
LikeLike
I look forward for this also.
LikeLike
hi Can.. yes it works quite well for directory listing. but on files above 2 or 5 MB (jpg pictures from smartphones) after a while the error happens and download crashes.
readBuffer = await stream.ReadAsync(buffer, bufferSize, InputStreamOptions.Partial)
Error:
A first chance exception of type ‘System.ObjectDisposedException’
actual code
readBuffer = await stream.ReadAsync(buffer, bufferSize, InputStreamOptions.Partial);
iLoop = iLoop + 1;
downloaded = resultingBuffer.Length;
//—-
if (readBuffer.Length == 0)
{
//
fl_Sys_Messagebox(“readbuffer.length==0”, “IsEOF”);
IsEOF = true;
break; //to reload
//
}
else if(readBuffer.Length < bufferSize)
{
//
System.Diagnostics.Debug.WriteLine(“buffer to End: ” + downloaded);
//
}
//—-
resultingBuffer = resultingBuffer.Concat(readBuffer.ToArray()).ToArray();
System.Diagnostics.Debug.WriteLine(iLoop + ” buffer:” + downloaded);
if (iLoop % 300 == 0)
{
System.Diagnostics.Debug.WriteLine(iLoop + ” keepalive”);
KeepAlive();
}
LikeLike
.. as a ftp server i use my smartphone >FTP Server Plus (simple but efficient)
the app will download pictures from the smartphone to the central nas-fileserver.
win 10 background transfer would also work for that but on win10 apps there is no possibility to run ftp upload stuff.
http://www.microsoft-programmierer.de/Details?d=1306&a=13&f=207&l=0&t=Android-App:-FTP-Server-Plus—-Auf-Dateien-des-Smartphones-im-Wlan-zugreifen
LikeLike
Also i have the same error of objectdisposed if file is bigger.
No solution for this?
LikeLike
I have the same problem of ObjectDisposedException with big files, no news on this problem?
LikeLike
can someone send out a repro project… all my tests come out positive both on local and hosted ftp server using the openreadstream method. 😦
LikeLike
Hi
I have this problem in release version of app also with ftp in my local network on another machine with file of 50 mb.
I use exactly your code in my uwp app. Are you sure you haven’t change your code from last dated 2014 I have download?
Thanks for your work!
LikeLike
Hi,
i have edit your library with a new project for reproduce the problem. For reproduce install filezille on another machine and insert a big file like 100mb
launch app from another machine, sometimes works in debug but never in relese.
i hope you can solve this problem because your library is very good!!
https://onedrive.live.com/redir?resid=E8C623E54F96BDF1!283720&authkey=!APiK3pcDG1HOc08&ithint=file%2czip
thanks!!
LikeLike
Hey, thanks man, I will take a look at it tonight…
LikeLike
Very good news 😀 i really need your library 😛 If you need some help to test you can contact me at acquariusoft[at]gmail[dot]com
thanks again!!!
LikeLike
Hi,
have you see my project?
i have see a strange things that i hope can help you, if i add a Debug.WriteLine(byteRead.Length);
line all seems to work in debug but if i add some milliseconds of wait don’t work. naturally in release writeline not work 😦
LikeLike
Hey, I did run some tests… I did get the object closed exception after 600mb downloaded. I m thinking it might be related to the memory resources growing way out of proportion(I.e. The memory heap was around 650mb when it errored out). I will try to write it directly to storage next. As you said I had bunch of trace calls as well in the code.
LikeLike
Hi,
i don’t think is memory problem, if i remove all write code and leave only
while (true)
{
await stream.ReadAsync(newBuf, BUFFER_SIZE, InputStreamOptions.Partial);
}
i have the same problem and with memory profile the app don’t use more than 25mb of ram and appear don’t grow.
LikeLike