views:

386

answers:

1

It's not possible to set a connection timeout on a .NET remoting call. Documentation occasionally refers to TcpChannel properties that allegedly do this, but discussions and the most recent docs I've found indicate that this is not possible. One may set a timeout on the remoting call itself, but not on the initial connection attempt. You're stuck with the default 45-second timeout.

For various reasons I can't use WCF.

This causes a problem when the remoting server goes away. If I attempt to make a remoting call, I'm stuck for those 45 seconds. That's not good. I want to check for the presence of the remoting server. Pinging it with a PingTimeout is the simplest approach, but I want to check specifically for the remoting server, as opposed to just the machine being up.

After some experimentation, I've come up with this approach:

  1. Asynchronously begin a TCP socket connection to the remoting port.
  2. Wait for the connection to complete, or a timeout to expire (using a ManualResetEvent).
  3. If the connection async callback succeeded, return success. Otherwise, return failure.

This works, but I'm unsure about my use of my WaitHandle and socket. I'd also like to assure thread-safety WRT concurrent checks, which I think I've done. My code's below. Do you see any problems with my approach?

private static bool IsChannelOpen(string ip, int port, int timeout)
{
    IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ip), port);
    Socket client = new Socket(endpoint.AddressFamily,
              SocketType.Stream, ProtocolType.Tcp);
    SocketTestData data = new SocketTestData()
              { Socket = client, ConnectDone = new ManualResetEvent(false) };
    IAsyncResult ar = client.BeginConnect
              (endpoint, new AsyncCallback(TestConnectionCallback), data);

    // wait for connection success as signaled from callback, or timeout 
    data.ConnectDone.WaitOne(timeout);
    client.Close();
    return data.Connected;
}

private static void TestConnectionCallback(IAsyncResult ar)
{
    SocketTestData data = (SocketTestData)ar.AsyncState;
    data.Connected = data.Socket.Connected;
    if (data.Socket.Connected)
    {
        data.Socket.EndConnect(ar);
    }
    data.ConnectDone.Set(); // signal completion
}

public class SocketTestData
{
    public Socket Socket { get; set; }
    public ManualResetEvent ConnectDone { get; set; }
    public bool Connected { get; set; }
}
+1  A: 

Your approach seems fine, but would be more inclined to wrap the socket code in a using to ensure no resource leak in IsChannelOpen function, and wrap it around the try/catch block also, as if the connection failed you'll get a nasty surprise if a socket exception occurred and your code jumps off into the woods never to be seen again.

Also, the other thing that I notice is in your asynchronous call back function, what happens if the connect failed - i.e. EndConnect does not get called in order to complete the asynchronous callback?

Here is what I think to robust-ify the code

        private static bool IsChannelOpen(string ip, int port, int timeout)
        {
            IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ip), port);
            SocketTestData data;
            try
            {
                using (Socket client = new Socket(endpoint.AddressFamily,
                          SocketType.Stream, ProtocolType.Tcp))
                {

                    data = new SocketTestData() { Socket = client, ConnectDone = new ManualResetEvent(false) };
                    IAsyncResult ar = client.BeginConnect
                              (endpoint, new AsyncCallback(TestConnectionCallback), data);

                    // wait for connection success as signaled from callback, or timeout 
                    data.ConnectDone.WaitOne(timeout);
                }
            }
            catch (System.Net.Sockets.SocketException sockEx)
            {
            }
            catch (System.Exception ex)
            {
            }
            return data.Connected;
        }

        private static void TestConnectionCallback(IAsyncResult ar)
        {
            SocketTestData data = (SocketTestData)ar.AsyncState;
            data.Connected = data.Socket.Connected;
            data.Socket.EndConnect(ar);
            data.ConnectDone.Set(); // signal completion
        }

        public class SocketTestData
        {
            public Socket Socket { get; set; }
            public ManualResetEvent ConnectDone { get; set; }
            public bool Connected { get; set; }
        }

What do you think?

Hope this helps, Best regards, Tom.

tommieb75
@Tom: Excellent suggestions, thanks. I've added the `using` block. I don't **think** I need the `try/catch` block, as that exception's thrown from a synchronous call to `IsChannelOpen`, and I may want to propagate the exception. One of my earlier attempts had an unconditional call to `EndConnect`, but that fails on connection failure, with a SocketException ("No connection could be made because the target machine actively refused it"). That's why I added the conditional, and part of why I asked this question. :-)
Michael Petrotta
Hmmm... `System.Net.Sockets.Socket` doesn't implement `Dispose`. try/catch/finally it is...
Michael Petrotta
Interesting...in VS 2008, I can use the socket in a using clause (.NET Framework 3.5). Would not have thought it in VS 2005 (i.e. .NET Framework 2)
tommieb75
I'm using 3.5 too, and I can wrap the socket creation in a using block, but unless the target of a using clause implements IDisposable (which Socket does not), it doesn't buy you anything (to the best of my knowledge).
Michael Petrotta