Handling Disconnects

Topics: Developers
Dec 21, 2011 at 1:59 AM

I am having some difficulty implementing my own server manager (for automated hash logging, chat logging) when the server disconnects the client.

I have effectively stripped down the code to the point where the client connects, logs in with the password and idles. A timer starts which triggers every 2 minutes to check the connection state of the client. If the client is disconnected, it tries to connect it (by using the Connect method). When that happens, I get this:

Unhandled Exception: System.Net.Sockets.SocketException: A request to send or re
ceive data was disallowed because the socket is not connected and (when sending
on a datagram socket using a sendto call) no address was supplied
   at System.Net.Sockets.Socket.BeginReceive(Byte[] buffer, Int32 offset, Int32
size, SocketFlags socketFlags, AsyncCallback callback, Object state)
   at System.Net.Battlefield3.RconClient.Receive(RconClient client)
   at System.Net.Battlefield3.RconClient.PostConnect(RconClient client)
   at System.Net.Battlefield3.RconClient.ConnectCallback(IAsyncResult ar)
   at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
   at System.Net.ContextAwareResult.CompleteCallback(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext,
ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext,
ContextCallback callback, Object state)
   at System.Net.ContextAwareResult.Complete(IntPtr userToken)
   at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
   at System.Net.LazyAsyncResult.InvokeCallback(Object result)
   at System.Net.Sockets.Socket.MultipleAddressConnectCallback(IAsyncResult result)
   at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
   at System.Net.ContextAwareResult.Complete(IntPtr userToken)
   at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
   at System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32
 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32
errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)

 

Is it necessary to create a NEW client to connect, or can I safely use the existing (now disconnected) client and I'm just doing something wrong?

Coordinator
Dec 21, 2011 at 2:46 PM

I may have found the problem, although I'm not sure. The library is designed to dispose of it's TcpClient whenever it's not connected, so it's odd that the attempted Receive throws an exception for not being connected, meaning it's not disposed. I did find a bug where, due to recent changes in connect code, the PostConnect method would be done even if the connect fails. If this is your problem, it would also mean your RconClient is failing to connect at that time (which is why I'm not sure if it's the problem or not). I guess this problem would occur when servers are restarting, though. I'm able to to connect, disconnect, and reconnect just fine, so I can't reproduce your problem myself.

I'll be checking in an update that would fix this after I finish with another thing I'm working on, but, if you'd like to fix it now, do this to either the latest code or the code from the last release:

Go to the ConnectCallback method (in the Async Socket Methods region), and, above the ThreadPool code, add Disconnect(client, ex.Message, ex.SocketErrorCode, false);. Also, below that ThreadPool code, add return;.

I haven't tested that code because I have other code I'm working on that doesn't compile, but it'll hopefully work. The way you can test it is to just unplug your internet when trying to connect.

Dec 21, 2011 at 3:30 PM

Thanks for the reply Tim.

My initial thought was that perhaps there was some work done in the construction of the object that is present during the first connect, but doesn't get reset on the second connect. Is there possibly any different between what happens to the object when the client disconnects itself and when the server disconnects the client?

I'll be checking this out tonight and will provide feedback.

Coordinator
Dec 21, 2011 at 4:13 PM
Edited Dec 21, 2011 at 4:35 PM

Nah. Whenever a SocketException is caught from the asynchronous socket methods, the same code is run. Either way, that problem would have shown up when I tested connecting, disconnecting, and reconnecting. Another way you can test to see if it's the problem I pointed out earlier is to try and connect with your internet unplugged.

 

Edit: Since I didn't build a library with the fix, I decided to try connecting with my internet unplugged. Since I got the same exception, that's probably the problem.

Coordinator
Dec 21, 2011 at 8:34 PM

The newest code has the fix for connecting :D

Dec 21, 2011 at 11:31 PM

I updated the code to show:

static void ConnectCallback(IAsyncResult ar)
        {
            RconClient client = (RconClient)ar.AsyncState;
            try
            {
                client.Socket.EndConnect(ar);
            }
            catch (SocketException ex)
            {
                Disconnect(client, ex.Message, ex.SocketErrorCode);
                ThreadPool.QueueUserWorkItem(new WaitCallback(RaiseConnectErrorThread), new object[] { client, ex });
                return;
            }
            finally
            {
                client.Connecting = false;
            }
            PostConnect(client);
        }

 

Now, however, I do not see the Disconnect event fire when the server goes down (I have access to start and stop our server).

Coordinator
Dec 22, 2011 at 1:40 AM

It turns out that fix ended up revealing another exception D:

The latest code fixes this new problem, but, if you'd like to implement it yourself, change the line of code dealing with PingTimer in RconClient.Reset() to

if (PingTimer != null) { PingTimer.Dispose(); PingTimer = null; }
Coordinator
Dec 22, 2011 at 1:48 AM

Also, RconClient won't be raising the Disconnected event when a connection fails to establish because you can't really disconnect if there is no connection; the ConnectError event should be used for this. I wouldn't keep your code using Disconnect(client, ex.Message, ex.SocketErrorCode); unless you plan to change that line each release (which would certainly be a solution :D). I just wanted to warn you so your code doesn't break in the future.

Oh and if you're perhaps using RconClient in ASP.NET, consider using the new LightRconClient. There are advantages and disadvantages to using it; rather than going overboard and writing a paragraph about them, I'll only explain them if you are using it in an ASP.NET application.

If you have any more bugs (or feature requests), I'd love to fix (implement) them :D

Jan 1, 2012 at 8:27 PM

I've confirmed that now (with the manually updated DLL) that I do not get disconnect events after successfully connecting to the server and then shutting down the server.

Coordinator
Jan 5, 2012 at 2:32 PM

Sorry for the wait; I've been sick for a bit D:

Anyway, I don't currently have a server to test on, so can you see if the server sends a TCP FIN when you shutdown/restart the server?

Coordinator
Jan 14, 2012 at 4:59 PM

Since my community still doesn't have their server, I'm not able to observe what the server does when it gets shut down or restarts. One it's (hopefully) back up, I'll revisit this.

For now, though, since the code works fine whenever a TCP FIN is received (with the quit command), the server probably doesn't properly terminate the connection when being shut down. One option is to set RconClient.PingTimeout to 2 (which is the smallest value allowed due to how it's written). This would cause the RconClient to disconnect after two minutes of no packets being received. Another option is to periodically use RconClient.SendRequest with a command (or a fake one) and see if null is returned; you can probably reliably do that test in intervals of a few seconds if you feel your connection is reliable and doesn't lag.