3

I want to use StreamReader and StreamWriter to receive and send data over TCPClient.NetworkStream. The code is depicted below:
Client code:

 using (TcpClient client = new TcpClient())
            {
                IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9001);
                client.Connect(localEndPoint);
                client.NoDelay = true;

                using(NetworkStream stream = client.GetStream())
                {                    
                    using (StreamWriter writer = new StreamWriter(stream))
                    {
                        writer.AutoFlush = true;
                        using (StreamReader reader = new StreamReader(stream))
                        {    

                            string message = "Client says: Hello";
                            writer.WriteLine(message);

                // If I comment the line below, the server receives the  first message
                // otherwise it keeps waiting for data
                           string response = reader.ReadToEnd();
                           Console.WriteLine(response);
                        }
                       ;
                    }
                }
            }


Note: If I comment the reader.ReadToEnd(); then the server receives the message, otherwise the server keeps waiting for data from the client. Is ReadToEnd the problem?

Server code accepts a connection asynchronously, but handles the data in a synchronous manner:

class Program
    {
        private static AsyncCallback tcpClientCallback;
        static void Main(string[] args){

            IPEndPoint localEndPoint =  new IPEndPoint(IPAddress.Parse("127.0.0.1"),9001);
            TcpListener listener = new TcpListener(localEndPoint);

            listener.Start();

            tcpClientCallback = new AsyncCallback(ConnectCallback);
            AcceptConnectionsAysnc(listener);

            Console.WriteLine("Started Aysnc TCP listener, press any key to exit (server is free to do other tasks)");
            Console.ReadLine();
        }

        private static void AcceptConnectionsAysnc(TcpListener listener)
        {
            listener.BeginAcceptTcpClient(tcpClientCallback, listener);
        }

        static void ConnectCallback(IAsyncResult result)
        {
            Console.WriteLine("Received a conenct request");
            TcpListener listener = (TcpListener)result.AsyncState;

            // We are starting a thread which waits to receive the data
            // This is not exactly scalable
            Task recieveTask = new Task(() => { HandleRecieve(result, listener); });
            recieveTask.Start();

            AcceptConnectionsAysnc(listener);
        }


        // This makes a blocking call - that means until the client is ready to
        // send some data the server waits
        private static void HandleRecieve(IAsyncResult result, TcpListener listener)
        {
            Console.WriteLine("Waiting for data");

            using (TcpClient client = listener.EndAcceptTcpClient(result))
            {
                client.NoDelay = true;

                using (NetworkStream stream = client.GetStream())
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        using (StreamWriter writer = new StreamWriter(stream))
                        {
                            writer.AutoFlush = true;
                            Stopwatch watch = new Stopwatch();
                            watch.Start();
                            string data = reader.ReadToEnd();
                            watch.Stop();
                            Console.WriteLine("Time: " + watch.Elapsed.TotalSeconds);
                            Console.WriteLine(data);


// Send a response to client
                                writer.WriteLine("Response: " + data);
                            }

                        }
                    }            


                }

            }

        } 
coder_bro
  • 10,503
  • 13
  • 56
  • 88

1 Answers1

7

A stream doesn't end until it is closed. Currently, both client and server are holding their connections open, and both is trying to read to the end from the other - and when they do, will close themselves. This is a deadlock: neither will ever get to the end, so neither will ever close (which would allow the other to get to the end).

Options:

  • use raw sockets, and have the client close the outbound stream after sending:

    socket.Shutdown(SocketShutdown.Send);
    

    this lets the server read to completion, which lets the server close, which lets the client read to completion; the client can still read from the inbound stream after closing the outbound socket.

  • use a different termination metaphor than ReadToEnd; for text-based protocols, end-of-line is the most common (so: ReadLine); for binary protocols, a length-prefix of the message is the most common. This allows each end to read the next message, without ever having to read to the end of the stream. In this scenario it would only be a single message, but the strategy still works.

  • use a different protocol if you find the above tricky; http is good for single request/response operations, and can be handled at the server via HttpListener.

Additionally, I'd be very careful about using ReadToEnd on a server (or even ReadLine) - that could be a good way of locking up threads on the server. If the server needs to cope with high throughput and lots of connections, async-IO based on raw sockets would be preferred.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Yes ReadToEnd was the issue, replacing it with ReadLine helped. – coder_bro Oct 30 '12 at 07:40
  • @Ngm personally I would still have reservations about using `ReadLine` on a server - see my last paragraph. I have some TCP servers that do this type of thing, and even looking for and end-of-line I use async-IO and just scan for bytes 13/10. – Marc Gravell Oct 30 '12 at 07:41
  • Thanks Marc,so what is the best option in case I do not want to use Raw sockets and use streams? – coder_bro Oct 30 '12 at 07:42
  • @Ngm you're tying my hands a bit there... that is "what is the best option if we assume I don't want to use the best option" (and btw, you can use *sockets* without use the `Stream` class). The `Socket` class gives you the best async options, but `TcpClient` gives you *most* of that via `GetStream()` then `BeginRead()` on the stream. It really depends how critical the server is, and what the throughput is. `StreamReader` *will work*... as long as your clients are well-behaved. – Marc Gravell Oct 30 '12 at 07:46