简体   繁体   中英

Problems with closing TcpClient and NetworkStream

currently I am working on a simple client server chat program (wanted to get into client server communication things in C#). It works so far except for disconnecting properly from the server.

On the client I use this code here to close the connection:

client.Client.Disconnect(false); // client is the TcpClient
client.Close();

On the server there is a threaded loop waiting for messages from the client:

private void StartChat()
{
    int requestCount = 0;
    byte[] bytesFrom = new byte[10025];
    string dataFromClient = null;
    string rCount = null;

    while (true)
    {
        try
        {
            requestCount++;

            NetworkStream stream = tcpClient.GetStream();

            int bufferSize = (int)tcpClient.ReceiveBufferSize;
            if (bufferSize > bytesFrom.Length)
            {
                bufferSize = bytesFrom.Length;
            }

            stream.Read(bytesFrom, 0, bufferSize);
            dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom);
            dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"));
            rCount = Convert.ToString(requestCount);

            string message = client.Name + " says: " + dataFromClient;
            program.Broadcast(message);

        }
        catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException)
        { 
            program.UserDisconnected(client);
            break;
        }
        catch(ArgumentOutOfRangeException ex)
        {
            Debug.WriteLine(ex.ToString());
            break;
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex.ToString());
            break;
        }
    }

If the client disconnects with the code shown above the function is fetching the stream all the time and producing such an output:

\\0\\0\\0\\0\\0\\0\\0 [and so on]

In this case the ArgumentOutOfRangeException will be thrown because there is no index of $ . I added a break to avoid and endless execution of the loop.

Surprisingly the ObjectDisposedException will not be thrown. Also the System.IO.IOException will not be thrown however it should because the stream was closed so the connection has been refused.

If I just close the client application which is connected to the server the server doesn't stops the loop, it just waits for a stream which will never come because the client disconnected.

So how could I detect if the client is disconnected or not reachable any more? And is the way I am closing the client connection a proper way to close a connection?

Thanks for your help!

Update:

private void StartChat()
{
    int requestCount = 0;
    byte[] bytesFrom = new byte[10025];
    string dataFromClient = null;
    string rCount = null;

    while (true)
    {
        try
        {
            requestCount++;

            NetworkStream stream = tcpClient.GetStream();
            stream.ReadTimeout = 4000;

            int bufferSize = (int)tcpClient.ReceiveBufferSize;
            if (bufferSize > bytesFrom.Length)
            {
                bufferSize = bytesFrom.Length;
            }


            // Wait for a client message. If no message is recieved within the ReadTimeout a IOException will be thrown
            try
            {
                int bytesRead = stream.Read(bytesFrom, 0, bufferSize);
                stream.Flush();

                if (bytesRead == 0)
                {
                    throw new System.IO.IOException("Connection seems to be refused or closed.");
                }
            }
            catch (System.IO.IOException)
            {
                byte[] ping = System.Text.Encoding.UTF8.GetBytes("%");
                stream.WriteTimeout = 1;

                stream.Write(ping, 0, ping.Length);
                continue;
            }


            dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
            dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"));
            rCount = Convert.ToString(requestCount);

            string message = client.Name + " says: " + dataFromClient;
            program.Broadcast(message);

        }
        catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException)
        {
            Debug.WriteLine(ex.ToString());
            program.UserDisconnected(client);
            break;
        }
        catch(ArgumentOutOfRangeException ex)
        {
            Debug.WriteLine(ex.ToString());
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex.ToString());
            break;
        }
    }
}

You should check the return value of stream.Read - it returns the number of bytes actually read.

This will be 0 when the client has disconnected, and when it does read data, the number of bytes read are often less than the buffer size.

int bytes = stream.Read(bytesFrom, 0, bufferSize);
if (bytes == 0)
{
    // client has disconnected
    break;
}

dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom, 0, bytes);

In response to your recent comment, three things can happen when a client terminates its connection:

  • The client closes the connection correctly, and you receive 0 bytes
  • Something detectable happened, causing an exception
  • The client is "gone" but no signal is sent to your end

In this last situation the server will still act as if there is an active connection, until it tries to send data, which would obviously fail. This is why many protocols work with a connection timeout and/or a keep-alive mechanism.

int bytesRead = stream.Read(...); if (bytesRead == 0) // client disconneted

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM