3

我想使用 StreamReader 和 StreamWriter 通过 TCPClient.NetworkStream 接收和发送数据。代码如下图所示:
客户端代码:

 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);
                        }
                       ;
                    }
                }
            }


注意:如果我评论 reader.ReadToEnd(); 然后服务器接收到消息,否则服务器一直等待来自客户端的数据。ReadToEnd 是问题吗?

服务器代码异步接受连接,但以同步方式处理数据:

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);
                            }

                        }
                    }            


                }

            }

        } 
4

1 回答 1

7

流在关闭之前不会结束。目前,客户端和服务器都保持他们的连接打开,并且都试图从另一个读取到最后- 当他们这样做时,它们会关闭自己。这是一个僵局:两者都不会走到尽头,所以也永远不会关闭(这将允许对方走到尽头)。

选项:

  • 使用原始套接字,并让客户端在发送后关闭出站流:

    socket.Shutdown(SocketShutdown.Send);
    

    这让服务器读取完成,让服务器关闭,让客户端读取完成;关闭出站套接字后,客户端仍然可以从入站流中读取。

  • 使用与ReadToEnd;不同的终止隐喻 对于基于文本的协议,行尾是最常见的(所以:)ReadLine;对于二进制协议,消息的长度前缀是最常见的。这允许每一端读取下一条消息,而不必读取到流的末尾。在这种情况下,它只是一条消息,但该策略仍然有效。

  • 如果您发现上述问题很棘手,请使用不同的协议;http 适合单请求/响应操作,可以在服务器上通过HttpListener.

ReadToEnd此外,在服务器(甚至)上使用时我会非常小心ReadLine——这可能是锁定服务器上线程的好方法。如果服务器需要处理高吞吐量和大量连接,则首选基于原始套接字的异步 IO。

于 2012-10-30T07:37:53.707 回答