How to detect a Socket Disconnect in C#

18,423

One option is to use TCP keep alive packets. You turn them on with a call to Socket.IOControl(). Only annoying bit is that it takes a byte array as input, so you have to convert your data to an array of bytes to pass in. Here's an example using a 10000ms keep alive with a 1000ms retry:

Socket socket; //Make a good socket before calling the rest of the code.
int size = sizeof(UInt32);
UInt32 on = 1;
UInt32 keepAliveInterval = 10000; //Send a packet once every 10 seconds.
UInt32 retryInterval = 1000; //If no response, resend every second.
byte[] inArray = new byte[size * 3];
Array.Copy(BitConverter.GetBytes(on), 0, inArray, 0, size);
Array.Copy(BitConverter.GetBytes(keepAliveInterval), 0, inArray, size, size);
Array.Copy(BitConverter.GetBytes(retryInterval), 0, inArray, size * 2, size);
socket.IOControl(IOControlCode.KeepAliveValues, inArray, null);

Keep alive packets are sent only when you aren't sending other data, so every time you send data, the 10000ms timer is reset.

Share:
18,423
DigitalJedi805
Author by

DigitalJedi805

Software engineer from Central California, self taught since 2000, college educated since 2008, industry experience since 2010.

Updated on July 19, 2022

Comments

  • DigitalJedi805
    DigitalJedi805 almost 2 years

    I'm working on a client/server relationship that is meant to push data back and forth for an indeterminate amount of time.

    The problem I'm attempting to overcome is on the client side, being that I cannot manage to find a way to detect a disconnect.

    I've taken a couple of passes at other peoples solutions, ranging from just catching IO Exceptions, to polling the socket on all three SelectModes. I've also tried using a combination of a poll, with a check on the 'Available' field of the socket.

    // Something like this
    Boolean IsConnected()
    {
        try
        {
            bool part1 = this.Connection.Client.Poll(1000, SelectMode.SelectRead);
            bool part2 = (this.Connection.Client.Available == 0);
    
            if (part1 & part2)
            {
                // Never Occurs
                //connection is closed
                return false;
            }
            return true;
        }
        catch( IOException e )
        {
            // Never Occurs Either
        }
    }
    

    On the server side, an attempt to write an 'empty' character ( \0 ) to the client forces an IO Exception and the server can detect that the client has disconnected ( pretty easy gig ).

    On the client side, the same operation yields no exception.

    // Something like this
    Boolean IsConnected( )
    {
        try
        {
    
            this.WriteHandle.WriteLine("\0");
            this.WriteHandle.Flush();
            return true;
        }
        catch( IOException e )
        {
            // Never occurs
            this.OnClosed("Yo socket sux");
            return false;
        }
    }
    

    A problem that I believe I am having in detecting a disconnect via a poll, is that I can fairly easily encounter a false on a SelectRead, if my server hasn't yet written anything back to the client since the last check... Not sure what to do here, I've chased down every option to make this detection that I can find and nothing has been 100% for me, and ultimately my goal here is to detect a server (or connection) failure, inform the client, wait to reconnect, etc. So I am sure you can imagine that this is an integral piece.

    Appreciate anyone's suggestions. Thanks ahead of time.

    EDIT: Anyone viewing this question should note the answer below, and my FINAL Comments on it. I've elaborated on how I overcame this problem, but have yet to make a 'Q&A' style post.

    • user207421
      user207421 almost 12 years
      Just catch IOExceptions, and use a read timeout. You don't need all this other malarkey.
    • DigitalJedi805
      DigitalJedi805 almost 12 years
      I've tried that already ( incase you didn't really read my post... ) and it's hit or miss. A read operation timing out after a second causes an IO, which would force a disconnect... But what if I just haven't received data...?
    • user207421
      user207421 almost 12 years
      I read your post. It isn't 'hit and miss', it is subject to asynchronous data buffering both locally and remotely. You won't get an exception on the first write to a failed connection, as it hasn't been detected yet: you will get it on a subsequent write, after TCP has timed out the retry attempts.
    • DigitalJedi805
      DigitalJedi805 almost 12 years
      Sorry to say I strongly disagree. I can Fraps a Debug session if you would like...
    • escape-llc
      escape-llc about 9 years
      Socket.Receive and EndReceive() return zero when remote side has done a shutdown on its side. This is documented in API description: If you are using a connection-oriented Socket, the Receive method will read as much data as is available up to the size of the buffer. If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received, the Receive method will complete immediately and return zero bytes.
  • DigitalJedi805
    DigitalJedi805 almost 12 years
    Awesome, thanks Joel, I'll be sure to give that a spin as soon as I hit my Dev system.
  • DigitalJedi805
    DigitalJedi805 almost 12 years
    Hey Joel, so I think I get the idea here but please correct me if Im wrong; The IOControl method puts a timed delivery of a 'KeepAlive' packet on a socket. I added ( almost word for word ) the above code to both my SocketServer.Client, and SocketClient constructors. I reduced the timer down to a second ( since that's the timeout on my 'read' on both sides? ) but my StreamReader.ReadLine ( Just realized as Im typing this that this shouldnt be my strategy ) never catches any data... I must say that I'm not sure exactly why we're putting what we're putting into the array; can I line-terminate it?
  • DigitalJedi805
    DigitalJedi805 almost 12 years
    Actually just tried running the same thing with a StreamReader.Read call, and still never got anything in either direction. Might be that I don't fully understand the concept.
  • Joel Rondeau
    Joel Rondeau almost 12 years
    @DigitalJedi805 - I'm reasonably certain the keep alive is eaten before you ever see anything. To test it, run your apps on different machines and run wireshark to look at the data. You should see the keep alive go out and a reply come back (if everything is connected), but your read() call won't see anything.
  • DigitalJedi805
    DigitalJedi805 almost 12 years
    Okay, I'll give that a spin, but I am also curious if the KeepAlive is meant to prevent the ReadTimeout from occurring? It seems like if I can accomplish that in this manner I'll be exactly where I need to, but right now with KeepAlives on both sides I'm still getting an exception based on my ReadTimeout.
  • DigitalJedi805
    DigitalJedi805 almost 12 years
    If not I just considered that I could create another thread that pushes an empty line every X milliseconds in both directions... Might be an easy solution, if I'm not already staring at one.
  • Joel Rondeau
    Joel Rondeau almost 12 years
    I don't think it is going to prevent your ReadTimeout. It's purpose is to know that the connection has gone down when you wouldn't otherwise know. What I use it for is when I have a client connection that has no timing requirements. If I don't use the keep alives, then the other side can go down and I won't know for hours. With the keep alives, I know in about 15 seconds (given my settings).
  • Joel Rondeau
    Joel Rondeau almost 12 years
    Your empty line theory is a good one. When I'm in charge of a protocol, I'll make my own keep alive message. I only use TCP keep alives if I don't have control over the protocol. Pretty sure I read that somewhere. Not going to be able to remember where.
  • DigitalJedi805
    DigitalJedi805 almost 12 years
    you got me there bud. With KeepAlives, ReadTimeouts and an empty line push I detect disconnects on either the client or server side within a second of it happening, without disconnecting if I'm not yet pushing data. Essentially, mission accomplished. Once I'm done with all of this I'm probably going to be posting a DLL with a lot of generic implementations like this, may interest you in the future. I'll be sure to let you know when I do. Thanks for all your help bud, you're the man ;)
  • Carlos
    Carlos over 9 years
    Just to clarify, if you use .IOControl, you don't need to .SetSocketOption(...KeepAlive...)?
  • Joel Rondeau
    Joel Rondeau over 9 years
    @Carlos That is correct. When using this, it isn't necessary to call SetSocketOption().