The code's not perfect, but I think it is good enough for now. If anyone has any better ideas how to implement it, please let me know. Otherwise, if you're interested, here ya go. Sorry if it looks nasty with the lack of window width. My code tends to run a little wide. :)
The reason I did this is because the FTPClient constructor blocks until it makes a connection. And if a machine I'm trying to hit goes offline, my app won't paint. So this resolves that by making the connection attempt asynchronous.
First are the changes I made to FTPClient.cs. Nothing exciting here. Just a tri-state enum for the connection state and a helper to get that state.
public enum FTPConnectionState
{
UNKNOWN = 0,
CONNECTED = 1,
DISCONNECTED = 2
}
public FTPConnectionState IsConnected()
{
return control.IsConnected();
}
Second are the changes I made to FTPControlSocket.cs. The major changes here are converting Resolve to use BeginResolve/EndResolve and Connect to use BeginConnect/EndConnect.
private FTPConnectionState connectionState = FTPConnectionState.UNKNOWN;
public FTPConnectionState IsConnected()
{
return connectionState;
}
public FTPControlSocket(string remoteHost, int controlPort,
StreamWriter log, int timeout)
{
// resolve remote host & take first entry
ResolveStateObject soResolve = new ResolveStateObject();
soResolve.m_ControlPort = controlPort;
soResolve.m_Log = log;
soResolve.m_Timeout = timeout;
Dns.BeginResolve( remoteHost, new AsyncCallback( ResolveForControlSocketCallback ), soResolve );
}
private void ResolveForControlSocketCallback( IAsyncResult ar )
{
try
{
ResolveStateObject soResolve = (ResolveStateObject) ar.AsyncState;
IPHostEntry hostEntry = soResolve.m_IPHostEntry;
hostEntry = Dns.EndResolve( ar );
IPAddress[] ipAddresses = hostEntry.AddressList;
Initialize(ipAddresses[0], soResolve.m_ControlPort, soResolve.m_Log, soResolve.m_Timeout);
}
catch
{
connectionState = FTPConnectionState.DISCONNECTED;
}
}
internal void Initialize(System.Net.IPAddress remoteAddr,
int controlPort, StreamWriter log,
int timeout)
{
LogStream = log;
// ensure we get debug from initial connection sequence
DebugResponses(true);
// establish socket connection & set timeouts
IPEndPoint ipe = new IPEndPoint(remoteAddr, controlPort);
controlSock =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Timeout = timeout;
controlSock.BeginConnect( ipe, new AsyncCallback( ConnectForControlSocketCallback ), controlSock );
}
private void ConnectForControlSocketCallback( IAsyncResult ar )
{
try
{
Socket socket = (Socket) ar.AsyncState;
socket.EndConnect( ar );
connectionState = FTPConnectionState.CONNECTED;
InitStreams();
ValidateConnection();
// switch off debug - user can switch on from this point
DebugResponses(false);
}
catch
{
connectionState = FTPConnectionState.DISCONNECTED;
}
}
class ResolveStateObject
{
public IPHostEntry m_IPHostEntry;
public int m_ControlPort;
public StreamWriter m_Log;
public int m_Timeout;
public ResolveStateObject()
{
m_IPHostEntry = null;
}
}
And finally, some unexciting changes I made in my code to work with the now asynchronous FTPClient constructor. Remember, it is no longer blocking with these changes, so I will block for it. But now I can do other stuff in my code such as DoEvents.
EnterpriseDT.Net.Ftp.FTPClient ftpClient = new EnterpriseDT.Net.Ftp.FTPClient( rmi.Address );
while( ftpClient.IsConnected() == EnterpriseDT.Net.Ftp.FTPConnectionState.UNKNOWN )
{
Application.DoEvents();
}
if( ftpClient.IsConnected() == EnterpriseDT.Net.Ftp.FTPConnectionState.DISCONNECTED )
{
MessageBox.Show( "Could not connect to remote machine", Define.APP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Exclamation ); // i18nbad
}