Sorv
Sorv

Reputation: 35

Why does my C# TcpClient fail to receive content from the server if the server sends before reading everything from the client?

In an application I'm working on I want to disconnect clients that are trying to send me packets that are too large. Just before disconnecting them I want to send them a message informing them about the reason for disconnecting them.

The issue I am running into is that the client cannot receive this server message, if the server does not read everything the client has send him first. I do not understand why this is happening.

I've managed to narrow it down to a very small test setup where the problem is demonstrated.

The StreamUtil class is a simple wrapper class that helps to get around the TCP message boundary problem, basically on the sender side it sends the size of each message first and then the message itself, and on the receiver side it receives the size of the message first and then the message.

The client uses a ReadKey command to simulate some time between sending and receiving, seeing in my real application these two actions are not immediately back to back either.

Here is a test case that works:

  1. Run server as shown below
  2. Run client as shown below, it will show a "Press key message", WAIT do not press key yet
  3. Turn off server since everything is already in the clients receive buffer anyway (I validated this using packet sniffer)
  4. Press key on the client -> client correctly shows the messages from the server.

This is what I was expecting, so great so far no problem yet.

Now in the server code, comment out the 2nd receive call and repeat the steps above. Step 1 and 2 complete successfully, no errors sending from client to server. On step 3 however the client crashes on the read from the server, EVEN though the server reply HAS arrived on the client (again validated with packet sniffer).

If I do a partial shutdown (eg socket.Shutdown (...send...)) without closing the socket on the server, everything works.

1: I just cannot get my head around WHY not processing the line of text from the client on the server causes the client to fail on receiving the text send back from the server.

2: If I send content from server to client but STOP the server before actually closing the socket, this content never arrives, but the bytes have already been transmitted to the server side... (see ReadKey in server to simulate, basically I block there and then just quit the server)

If anyone could shed light on these two issues, I'd deeply appreciate it.

Client:

class TcpClientDemo
{
    public static void Main (string[] args)
    {
        Console.WriteLine ("Starting....");
        TcpClient client = new TcpClient();

        try
        {
            client.Connect("localhost", 56789);

            NetworkStream stream = client.GetStream();

            StreamUtil.SendString(stream, "Client teststring...");

            Console.WriteLine("Press key to initiate receive...");
            Console.ReadKey();

            Console.WriteLine("server reply:" + StreamUtil.ReceiveString(stream));
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        finally
        {
            client.Close();
        }

        Console.WriteLine("Client ended");
        Console.ReadKey(true);
    }

}

Server:

class TcpServerDemo
{
    public static void Main (string[] args)
    {
        TcpListener listener = new TcpListener (IPAddress.Any, 56789);
        listener.Start ();
        Console.WriteLine ("Waiting for clients to serve...");

        while (true)
        {
            TcpClient client = null;
            NetworkStream stream = null;

            try
            {
                client = listener.AcceptTcpClient();
                stream = client.GetStream();

                //question 1: Why does commenting this line prevent the client from receiving the server reply??
                Console.WriteLine("client string:" + StreamUtil.ReceiveString(stream));

                StreamUtil.SendString(stream, "...Server reply goes here...");

                //question 2: If I close the server program without actually calling client.Close (while on this line), the client program crashes as well, why?
                //Console.ReadKey();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                break;
            }
            finally
            {
                if (stream != null) stream.Close();
                if (client != null) client.Close();
                Console.WriteLine("Done serving this client, everything closed.");
            }
        }

        listener.Stop();
        Console.WriteLine("Server ended.");
        Console.ReadKey(true);
    }

}

StreamUtil:

public class StreamUtil
{

    public static byte[] ReadBytes (NetworkStream pStream, int byteCount) {
        byte[] bytes = new byte[byteCount];
        int bytesRead = 0;
        int totalBytesRead = 0;

        try {
            while (
                totalBytesRead != byteCount && 
                (bytesRead = pStream.Read (bytes, totalBytesRead, byteCount - totalBytesRead)) > 0
            ) {
                totalBytesRead += bytesRead;
                Console.WriteLine("Read/Total:" + bytesRead + "/" + totalBytesRead);
            }
        } catch (Exception e) {
            Console.WriteLine(e.Message);
        }

        return (totalBytesRead == byteCount) ? bytes : null;
    }

    public static void SendString (NetworkStream pStream, string pMessage) {
        byte[] sendPacket = Encoding.ASCII.GetBytes (pMessage);
        pStream.Write (BitConverter.GetBytes (sendPacket.Length), 0, 4);
        pStream.Write (sendPacket, 0, sendPacket.Length);
    }

    public static string ReceiveString (NetworkStream pStream) {
        int byteCountToRead = BitConverter.ToInt32(ReadBytes (pStream, 4), 0);
        Console.WriteLine("Byte count to read:"+byteCountToRead);
        byte[] receivePacket = ReadBytes (pStream, byteCountToRead);

        return Encoding.ASCII.GetString (receivePacket);
    }

}

Upvotes: 2

Views: 2013

Answers (1)

Peter Wishart
Peter Wishart

Reputation: 12270

  1. The client fails because it detects the socket was already closed.
  2. If C# socket operations detect a closed connection during earlier operations, an exception is thrown on the next operation which can mask data which would otherwise have been received

The StreamUtil class does a couple of things when the connection is closed before/during a read:

  • Exceptions from the reads are swallowed
  • A read of zero bytes isn't treated

These obfuscate what's happening when an unexpected close hits the client.

Changing ReadBytes not to swallow exceptions and to throw a mock socket-closed exception (e.g. if (bytesRead == 0) throw new SocketException(10053);) when it reads zero bytes I think makes the outcome more clear.

Edit

I missed something subtle in your examples - your first example causes a TCP RST flag to be sent as soon as the server closes connection, due to the socket being closed with data waiting to be read.

The RST flag results in a closedown that doesn't preserve pending data.

This blog has some discussion based on a very similar scenario (web server sending a HTTP error).

So I don't think there's an easy fix, options are:

  • As you already tried, shutdown the socket on the server before closing to force a FIN to be sent before the RST
  • Read the data in question but never process it (taking up bandwidth for no reason)

Upvotes: 1

Related Questions