Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TcpListener: Detecting a client disconnect as opposed to a client not sending any data for a while

Tags:

c#

tcplistener

I was looking how to detect a 'client disconnect' when using a TcpListener.

All the answers seem to be similar to this one: TcpListener: How can I detect a client disconnect?

Basically, read from the stream and if Read() returns 0 the client had disconnected.

But that's assuming that a client disconnects after every single stream of data it sent. We operate in environments where the TCP connect/disconnect overhead is both slow and expensive.

We establish a connection and then we send a number of requests.

Pseudocode:

client.Connect();
client.GetStatus();
client.DoSomething();
client.DoSomethingElse();
client.AndSoOn();
client.Disconnect();

Each call between Connect and Disconnect() sends a stream of data to the server. The server knows how to analyze and process the streams.

If let the TcpListener read in a loop without ever disconnecting it reads and handles all the messages, but after the client disconnects, the server has no way of knowing that and it will never release the client and accept new ones.

var read = client.GetStream().Read(buffer, 0, buffer.Length);

if (read > 0) 
{
   //Process
}

If I let the TcpListener drop the client when read == 0 it only accepts the first stream of data only to drop the client immediately after.

Of course this means new clients can connect.

There is no artificial delay between the calls, but in terms of computer time the time between two calls is 'huge' of course, so there will always be a time when read == 0 even though that does not mean the client has or should be disconnected.

var read = client.GetStream().Read(buffer, 0, buffer.Length);

if (read > 0) 
{
   //Process
}
else
{
   break;   //Always executed as soon as the first stream of data has been received
}

So I'm wondering... is there a better way to detect if the client has disconnected?

like image 802
TimothyP Avatar asked Jul 26 '12 04:07

TimothyP


3 Answers

You could get the underlying socket using the NetworkStream.Socket property and use it's Receive method for reading.

Unlike NetworkStream.Read, the linked overload of Socket.Receive will block until the specified number of bytes have been read, and will only return zero if the remote host shuts down the TCP connection.


UPDATE: @jrh's comment is correct that NetworkStream.Socket is a protected property and cannot be accessed in this context. In order to get the client Socket, you could use the TcpListener.AcceptSocket method which returns the Socket object corresponding to the newly established connection.

like image 119
Eren Ersönmez Avatar answered Sep 17 '22 05:09

Eren Ersönmez


Eren's answer solved the problem for me. In case anybody else is facing the same issue here's some 'sample' code using the Socket.Receive method:

private void AcceptClientAndProcess()
{
    try
    {
        client = server.Accept();
        client.ReceiveTimeout = 20000;
    }
    catch
    {
        return;
    }

    while (true)
    {
        byte[] buffer = new byte[client.ReceiveBufferSize];
        int read = 0;

        try
        {
            read = client.Receive(buffer);
        }
        catch
        {
            break;
        }

        if (read > 0)
        {
            //Handle data
        }
        else
        {
            break;
        }
    }

    if (client != null)
        client.Close(5000);
}

You call AcceptClientAndProcess() in a loop somewhere.

The following line:

read = client.Receive(buffer);

will block until either

  • Data is received, (read > 0) in which case you can handle it
  • The connection has been closed properly (read = 0)
  • The connection has been closed abruptly (An exception is thrown)

Either of the last two situations indicate the client is no longer connected.

The try catch around the Socket.Accept() method is also required as it may fail if the client connection is closed abruptly during the connect phase.

Note that did specify a 20 second timeout for the read operation.

like image 42
TimothyP Avatar answered Sep 20 '22 05:09

TimothyP


The documentation for NetworkStream.Read does not reflect this, but in my experience, 'NetworkStream.Read' blocks if the port is still open and no data is available, but returns 0 if the port has been closed.

I ran into this problem from the other side, in that NetworkStream.Read does not immediately return 0 if no data is currently available. You have to use NetworkStream.DataAvailable to find out if NetworkStream.Read can read data right now.

like image 22
Denise Skidmore Avatar answered Sep 20 '22 05:09

Denise Skidmore