85

How can I detect that a client has disconnected from my server?

I have the following code in my AcceptCallBack method

static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
  //Accept incoming connection
  Socket listener = (Socket)ar.AsyncState;
  handler = listener.EndAccept(ar);
}

I need to find a way to discover as soon as possible that the client has disconnected from the handler Socket.

I've tried:

  1. handler.Available;
  2. handler.Send(new byte[1], 0, SocketFlags.None);
  3. handler.Receive(new byte[1], 0, SocketFlags.None);

The above approaches work when you are connecting to a server and want to detect when the server disconnects but they do not work when you are the server and want to detect client disconnection.

Any help will be appreciated.

cHao
  • 78,897
  • 19
  • 136
  • 168
  • 10
    @Samuel: The TCP and connection tags *are* very much relevant to this post in that TCP maintains a connection (whereas other network protocols such as UDP do not). – Noldorin Apr 06 '09 at 16:54
  • 3
    More on the heartbeat solution from my blog: [Detection of Half-Open (Dropped) Connections](http://nitoprograms.blogspot.com/2009/05/detection-of-half-open-dropped.html) – Stephen Cleary Apr 29 '10 at 13:50
  • The solution described here works well for me: http://stackoverflow.com/questions/1387459/how-to-check-if-tcpclient-connection-is-closed/29151608#29151608 – Rawk Mar 19 '15 at 20:01

14 Answers14

116

Since there are no events available to signal when the socket is disconnected, you will have to poll it at a frequency that is acceptable to you.

Using this extension method, you can have a reliable method to detect if a socket is disconnected.

static class SocketExtensions
{
  public static bool IsConnected(this Socket socket)
  {
    try
    {
      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
    }
    catch (SocketException) { return false; }
  }
}
Samuel
  • 35,821
  • 11
  • 83
  • 84
  • 1
    This worked. Thanks. I changed the method to return !(socket.Available == 0 && socket.Poll(1, SelectMode.SelectRead)); because I suspect socket.Available is faster than Socket.Poll() –  Apr 06 '09 at 18:52
  • The first parameter of Poll is the timeout in microseconds, I highly doubt you will notice 1 microsecond. ;) – Samuel Apr 06 '09 at 18:53
  • 29
    This method does not work unless the other end of the connection actually closes/shutdowns the socket. An unplugged network/power cable won't be noticed before the timeout period. The only way to be notified of disconnects right away is with a heartbeat function to continously check the connection. – Kasper Holdum Aug 27 '09 at 15:47
  • 8
    @Smart Alec: actually, you should use the example as it's shown above. There is a potential race condition if you change the order: if `socket.Available` returns 0 and you receive a packet just before `socket.Poll` gets called, `Poll` will return true and method will return `false`, although the socket is actually still healthy. – Groo May 28 '10 at 09:05
  • Actually you can test the NDIS device layer for loss of data link. That will tell if your cable is unplugged. – dviljoen Jan 08 '13 at 21:54
  • 4
    This works well 99% of the time, sometimes it gives a false disconnect though. – Matthew Finlay Jun 03 '13 at 05:29
  • 1
    This doesn't work in case of cable unplug. from MSDN about poll: "This method cannot detect certain kinds of connection problems, such as a broken network cable, or that the remote host was shut down ungracefully. You must attempt to send or receive data to detect these kinds of errors." – Lev Aug 07 '13 at 10:54
  • @KasperHoldum Do you have a link/something for this HeartBeat alike code? – Royi Namir Dec 22 '14 at 07:58
  • 5
    Just like Matthew Finlay noticed, this will report false disconnects sometimes as there is still a race condition between the result of the Poll method and checking the Available property. A packet could be *almost ready* to be read but not yet, thus Available being 0 - but a millisecond later, there's data to be read. A better option is to try to Receive one byte and the SocketFlags.Peek flag. Or implement some form of heartbeat and keep the connection status at a higher level. Or rely on the error handling at your send/receive methods (& callbacks, if using the async versions). – mbargiel Mar 16 '15 at 21:03
  • I've found attempting to send a single 0 value byte is one way to detect an ungracefull disconnect (such as an uplug) as this will throw an exception. – apc Nov 06 '15 at 14:22
  • Just use Poll for Write with 0 microseconds, Available uses a specific read / peek functionality which is actually slower than Poll always. – Jay May 26 '16 at 14:16
  • I second @mbargiel suggestion. If the 1-byte Receive function (with SocketFlags.Peek) returns zero means disconnected. Otherwise, even if it raises an exception (non-blocking / timeout waiting for data), assume the socket is still connected. – mycelo Oct 31 '17 at 19:27
24

Someone mentioned keepAlive capability of TCP Socket. Here it is nicely described:

http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

I'm using it this way: after the socket is connected, I'm calling this function, which sets keepAlive on. The keepAliveTime parameter specifies the timeout, in milliseconds, with no activity until the first keep-alive packet is sent. The keepAliveInterval parameter specifies the interval, in milliseconds, between when successive keep-alive packets are sent if no acknowledgement is received.

    void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval)
    {
        int size = Marshal.SizeOf(new uint());

        var inOptionValues = new byte[size * 3];

        BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
        BitConverter.GetBytes((uint)keepAliveTime).CopyTo(inOptionValues, size);
        BitConverter.GetBytes((uint)keepAliveInterval).CopyTo(inOptionValues, size * 2);

        socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
    }

I'm also using asynchronous reading:

socket.BeginReceive(packet.dataBuffer, 0, 128,
                    SocketFlags.None, new AsyncCallback(OnDataReceived), packet);

And in callback, here is caught timeout SocketException, which raises when socket doesn't get ACK signal after keep-alive packet.

public void OnDataReceived(IAsyncResult asyn)
{
    try
    {
        SocketPacket theSockId = (SocketPacket)asyn.AsyncState;

        int iRx = socket.EndReceive(asyn);
    }
    catch (SocketException ex)
    {
        SocketExceptionCaught(ex);
    }
}

This way, I'm able to safely detect disconnection between TCP client and server.

Volodymyr Kotylo
  • 423
  • 5
  • 10
Majak
  • 1,293
  • 1
  • 12
  • 28
  • 6
    Yes, set the keepalive+interval to a low value, any polls / sends should fail after keepalive+10*interval milliseconds. The 10 retries seem to be hardcoded since Vista? Works even if you unplug the cable unlike most other answers. This should be the accepted answer. – toster-cx Sep 23 '16 at 15:05
15

This is simply not possible. There is no physical connection between you and the server (except in the extremely rare case where you are connecting between two compuers with a loopback cable).

When the connection is closed gracefully, the other side is notified. But if the connection is disconnected some other way (say the users connection is dropped) then the server won't know until it times out (or tries to write to the connection and the ack times out). That's just the way TCP works and you have to live with it.

Therefore, "instantly" is unrealistic. The best you can do is within the timeout period, which depends on the platform the code is running on.

EDIT: If you are only looking for graceful connections, then why not just send a "DISCONNECT" command to the server from your client?

Erik Funkenbusch
  • 90,480
  • 27
  • 178
  • 274
  • 1
    Thanks but I actually mean a graceful software disconnection, not a physical disconnection. I'm running Windows –  Apr 06 '09 at 16:50
  • 1
    If he's only looking for graceful disconnections he doesn't have to send anything. His read will return end of stream. – user207421 Feb 22 '17 at 04:56
6

"That's just the way TCP works and you have to live with it."

Yup, you're right. It's a fact of life I've come to realize. You will see the same behavior exhibited even in professional applications utilizing this protocol (and even others). I've even seen it occur in online games; you're buddy says "goodbye", and he appears to be online for another 1-2 minutes until the server "cleans house".

You can use the suggested methods here, or implement a "heartbeat", as also suggested. I choose the former. But if I did choose the latter, I'd simply have the server "ping" each client every so often with a single byte, and see if we have a timeout or no response. You could even use a background thread to achieve this with precise timing. Maybe even a combination could be implemented in some sort of options list (enum flags or something) if you're really worried about it. But it's no so big a deal to have a little delay in updating the server, as long as you DO update. It's the internet, and no one expects it to be magic! :)

ATC
  • 69
  • 1
  • 1
3

Implementing heartbeat into your system might be a solution. This is only possible if both client and server are under your control. You can have a DateTime object keeping track of the time when the last bytes were received from the socket. And assume that the socket not responded over a certain interval are lost. This will only work if you have heartbeat/custom keep alive implemented.

Niran
  • 1,006
  • 1
  • 8
  • 10
2

I've found quite useful, another workaround for that!

If you use asynchronous methods for reading data from the network socket (I mean, use BeginReceive - EndReceive methods), whenever a connection is terminated; one of these situations appear: Either a message is sent with no data (you can see it with Socket.Available - even though BeginReceive is triggered, its value will be zero) or Socket.Connected value becomes false in this call (don't try to use EndReceive then).

I'm posting the function I used, I think you can see what I meant from it better:


private void OnRecieve(IAsyncResult parameter) 
{
    Socket sock = (Socket)parameter.AsyncState;
    if(!sock.Connected || sock.Available == 0)
    {
        // Connection is terminated, either by force or willingly
        return;
    }

    sock.EndReceive(parameter);
    sock.BeginReceive(..., ... , ... , ..., new AsyncCallback(OnRecieve), sock);

    // To handle further commands sent by client.
    // "..." zones might change in your code.
}
Şafak Gür
  • 6,389
  • 3
  • 52
  • 87
2

This worked for me, the key is you need a separate thread to analyze the socket state with polling. doing it in the same thread as the socket fails detection.

//open or receive a server socket - TODO your code here
socket = new Socket(....);

//enable the keep alive so we can detect closure
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

//create a thread that checks every 5 seconds if the socket is still connected. TODO add your thread starting code
void MonitorSocketsForClosureWorker() {
    DateTime nextCheckTime = DateTime.Now.AddSeconds(5);

    while (!exitSystem) {
        if (nextCheckTime < DateTime.Now) {
            try {
                if (socket!=null) {
                    if(socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0) {
                        //socket not connected, close it if it's still running
                        socket.Close();
                        socket = null;    
                    } else {
                        //socket still connected
                    }    
               }
           } catch {
               socket.Close();
            } finally {
                nextCheckTime = DateTime.Now.AddSeconds(5);
            }
        }
        Thread.Sleep(1000);
    }
}
JJ_Coder4Hire
  • 4,139
  • 1
  • 32
  • 23
1

The example code here http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx shows how to determine whether the Socket is still connected without sending any data.

If you called Socket.BeginReceive() on the server program and then the client closed the connection "gracefully", your receive callback will be called and EndReceive() will return 0 bytes. These 0 bytes mean that the client "may" have disconnected. You can then use the technique shown in the MSDN example code to determine for sure whether the connection was closed.

Bassem
  • 2,015
  • 25
  • 12
1

Expanding on comments by mbargiel and mycelo on the accepted answer, the following can be used with a non-blocking socket on the server end to inform whether the client has shut down.

This approach does not suffer the race condition that affects the Poll method in the accepted answer.

// Determines whether the remote end has called Shutdown
public bool HasRemoteEndShutDown
{
    get
    {
        try
        {
            int bytesRead = socket.Receive(new byte[1], SocketFlags.Peek);

            if (bytesRead == 0)
                return true;
        }
        catch
        {
            // For a non-blocking socket, a SocketException with 
            // code 10035 (WSAEWOULDBLOCK) indicates no data available.
        }

        return false;
    }
}

The approach is based on the fact that the Socket.Receive method returns zero immediately after the remote end shuts down its socket and we've read all of the data from it. From Socket.Receive documentation:

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.

If you are in non-blocking mode, and there is no data available in the protocol stack buffer, the Receive method will complete immediately and throw a SocketException.

The second point explains the need for the try-catch.

Use of the SocketFlags.Peek flag leaves any received data untouched for a separate receive mechanism to read.

The above will work with a blocking socket as well, but be aware that the code will block on the Receive call (until data is received or the receive timeout elapses, again resulting in a SocketException).

Community
  • 1
  • 1
Ian
  • 1,152
  • 1
  • 14
  • 27
0

Can't you just use Select?

Use select on a connected socket. If the select returns with your socket as Ready but the subsequent Receive returns 0 bytes that means the client disconnected the connection. AFAIK, that is the fastest way to determine if the client disconnected.

I do not know C# so just ignore if my solution does not fit in C# (C# does provide select though) or if I had misunderstood the context.

Aditya Sehgal
  • 2,731
  • 2
  • 24
  • 35
0

Using the method SetSocketOption, you will be able to set KeepAlive that will let you know whenever a Socket gets disconnected

Socket _connectedSocket = this._sSocketEscucha.EndAccept(asyn);
                _connectedSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);

http://msdn.microsoft.com/en-us/library/1011kecd(v=VS.90).aspx

Hope it helps! Ramiro Rinaldi

Rama
  • 430
  • 10
  • 14
  • 2
    It will not 'let you know whenever a socket gets disconnected'. It will *detect* it, eventually, if the keep-alive timer expires. By default it is set to two hours. You cannot describe that as 'whenever'. You also can't describe it as 'let[ting] you know: you still have to do a read or a write for the failure to be detected. -1 – user207421 Apr 01 '13 at 11:48
0

i had same problem , try this :

void client_handler(Socket client) // set 'KeepAlive' true
{
    while (true)
    {
        try
        {
            if (client.Connected)
            {

            }
            else
            { // client disconnected
                break;
            }
        }
        catch (Exception)
        {
            client.Poll(4000, SelectMode.SelectRead);// try to get state
        }
    }
}
0

This is in VB, but it seems to work well for me. It looks for a 0 byte return like the previous post.

Private Sub RecData(ByVal AR As IAsyncResult)
    Dim Socket As Socket = AR.AsyncState

    If Socket.Connected = False And Socket.Available = False Then
        Debug.Print("Detected Disconnected Socket - " + Socket.RemoteEndPoint.ToString)
        Exit Sub
    End If
    Dim BytesRead As Int32 = Socket.EndReceive(AR)
    If BytesRead = 0 Then
        Debug.Print("Detected Disconnected Socket - Bytes Read = 0 - " + Socket.RemoteEndPoint.ToString)
        UpdateText("Client " + Socket.RemoteEndPoint.ToString + " has disconnected from Server.")
        Socket.Close()
        Exit Sub
    End If
    Dim msg As String = System.Text.ASCIIEncoding.ASCII.GetString(ByteData)
    Erase ByteData
    ReDim ByteData(1024)
    ClientSocket.BeginReceive(ByteData, 0, ByteData.Length, SocketFlags.None, New AsyncCallback(AddressOf RecData), ClientSocket)
    UpdateText(msg)
End Sub
-1

You can also check the .IsConnected property of the socket if you were to poll.

theG
  • 577
  • 3
  • 9
  • 2
    This won't work for any of the scenarios (server / client) i presented above –  Apr 06 '09 at 16:51
  • 1
    Also, the property is called .Connected. – Qwertie Jun 24 '11 at 22:06
  • That doesn't detect arbitrary disconnections. You still have to do some I/O. – user207421 Apr 01 '13 at 11:49
  • @Jay No it doesn't. [The value of the `Connected` property reflects the state of the connection as of the most recent operation](https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected(v=vs.110).aspx). No polling. – user207421 Feb 22 '17 at 04:58