C# Async TCP Server 矫枉过正?

Posted

技术标签:

【中文标题】C# Async TCP Server 矫枉过正?【英文标题】:C# Async TCP Server overkill? 【发布时间】:2011-01-27 21:36:19 【问题描述】:

这确实是一个实施问题,所以我觉得最好从我的具体案例开始。

我有一个 C# 服务器,它从移动客户端异步侦听 TCP 连接。当一个移动客户端连接一个新线程时,客户端会发送一个小的(通常小于 100 字节)文本消息并接收一个类似大小的返回。服务器响应后,关闭连接并结束线程。

当前的基本用法是用户登录,检查内容有时长达 5 分钟,发送少量消息,从而在服务器上快速连续创建新线程 ,并且他们断开连接仅在几个小时后重新连接。此外,每个用户都有自己的服务器在他们的 PC 上运行,因此大多数服务器在任何给定时间都只会连接一个客户端,在极少数情况下会连接两个。

现在我遇到了以下错误,一个现有的连接被远程主机强行关闭,这让我想,我做错了吗? em>

所以我的问题:

    我当前的设置在这里合适吗? 如果是这样,我应该在发送一条小消息后结束线程,还是在给定的空闲时间后使其保持活动状态并关闭? 万一我做的一切都正确,极不可能,我是否应该通过在放弃前简单地重试几次来避免错误? 第四次也是最后一次,该错误完全杀死了服务器(服务器由另一个进程生成,任何未捕获的异常都会杀死它),如果我们已经做到了这一点,并且我的实现还可以,我该如何避免这种情况?

编辑:

在这里回答一些问题:

异常发生在我收到所有数据之前,但仅在用户快速连续发送多条消息的情况下发生。 我记得最大积压为 5,除非用户正在运行 Windows Server,但是我没有设置我的,也不知道默认值是什么,我会尝试将其显式设置为 5。

异步服务器代码:

    public void StartListening()
    
        //Data buffer for incoming data.
        byte[] bytes = new Byte[1024];

        //Establish the local endpoint for the socket.
        IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
        IPAddress ipAddress = ipHostInfo.AddressList[0];
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, Port);

        //Create a TCP/IP socket.
        Socket listener = new Socket(AddressFamily.InterNetwork,
            SocketType.Stream, ProtocolType.Tcp);
        listener.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.DontLinger,1);
        listener.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.ReuseAddress,1);

        //Bind the socket to the local endpoint and listen for
        //incoming connections.
        try
        
            listener.Bind(localEndPoint);
            listener.Listen(100);

            while (listening)
            
                //Set the event to nonsignaled state.
                allDone.Reset();    

                //Start an asychronous socket to listen for connections.
                Print("Waiting for a connection...");
                listener.BeginAccept(
                new AsyncCallback(AcceptCallback),
                    listener);

                //Wait until a connection is made before continuing.
                allDone.WaitOne();
            
        
        catch (Exception e)
        
            Print(e.ToString());    
        

        listener.Close();
    

    public void AcceptCallback(IAsyncResult arg)
    
        //Signal the main thread to continue.
        allDone.Set();


        try
        
            //Get the socket that handles the client request.
            Socket listener = (Socket) arg.AsyncState;
            Socket handler = listener.EndAccept(arg);


            //Create the state object.
            StateObject state = new StateObject();
            state.workSocket = handler;

            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReadCallback), state);
        
        catch (ObjectDisposedException ex)
        
            Print("Server terminated from another thread.");    
        
    

    public void ReadCallback(IAsyncResult arg)
    
        String content = String.Empty;

        //Retrieve the state object and the handler socket
        //from the asynchronous state object.
        StateObject state = (StateObject) arg.AsyncState;
        Socket handler = state.workSocket;

        //Read data from the client socket.
        int bytesRead = 0;
        try 
        
            bytesRead = handler.EndReceive(arg);
        
        catch (ObjectDisposedException ex)
        
            Print("Process was terminated from another thread.");   
        

        if (bytesRead > 0)
        
            //There might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(
                state.buffer,0,bytesRead));

            //Check for end-of-file tag. If it is not there, read
            //more data.
            content = state.sb.ToString();
            if (content.IndexOf("<EOF>") > -1)
            
                content = content.Remove(content.Length-6);
                //All the data has been read from the
                //client. Display it on the console.
                Print("Read " + content.Length + " bytes from socket. \n Data : " + content);
                Respond(handler, content);
            
            else
            
                //Not all data received. Get more.
                handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReadCallback), state);
            
        
    

    private void Send(Socket handler, String data)
    
        //Convert the string data to byte data using ASCII encoding.
        byte[] byteData = Encoding.ASCII.GetBytes(data);

        //Begin sending the data to the remote device.
        handler.BeginSend(byteData,0,byteData.Length,0,
            new AsyncCallback(SendCallback),handler);
    

    private void SendCallback(IAsyncResult arg)
    
        try
        
            //Retrieve the socket from the state object.
            Socket handler = (Socket) arg.AsyncState;

            //Complete sending the data to the remote device.
            int bytesSent = handler.EndSend(arg);
            Print("Sent " + bytesSent + " bytes to client.");

            handler.Shutdown(SocketShutdown.Both);
            //need to make this not linger around
            handler.LingerState = new LingerOption(true,1);
            handler.Close();
        
        catch (Exception e)
        
            Print(e.ToString());    
        
    

【问题讨论】:

这听起来像是一个 UDP 应用程序——与处理消息相比,您可能花费更多的时间和带宽进行三次 TCP 握手。 你可能是对的,如果我没记错的话,由于某种原因我不能使用 UDP,但我会再研究一下。 【参考方案1】:

理想情况下,您将使用 .NET 线程池,这比为每个连接创建一个新线程要高效得多。您能否分享您的确切“异步”代码 - 如果您在 TCPListener 上使用现有的异步模式,那么您可能已经在使用线程池。

关于异常,当您的客户端与服务器断开连接时,您会期望看到这种情况。它是否在您设法接收所有数据之前发生?你是在客户端刷新你的套接字吗?

就完全崩溃服务器而言,只需继续测试,并记录任何全局未处理的异常。这样您就可以了解所有可以预期的内容。

【讨论】:

而且使用WSAAsyncSelectWSAEventSelect 会比使用线程池更高效。 我已经添加了我的代码,异常发生在接收到所有数据之前,因此它很可能与最大数量的连接有关,因为异步它们都被立即接受但未处理。 【参考方案2】:

您可能想查看this article,其中列出了需要检查的几项内容。例如,当您在套接字上 .Listen() 时,您的 backlog 设置为什么?

【讨论】:

我没有设置它,我现在明确设置它并且我得到的错误实例较少,但它仍然会发生。我相信最大值是 5,除非您运行的是 Windows Server。不过我会看看这篇文章,看起来很有信息量。

以上是关于C# Async TCP Server 矫枉过正?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 5 中使用 async/await 模式编写高度可扩展的 TCP/IP 服务器?

Boost tcp_server async_write 错误:访问冲突写入位置

C# 类和只读成员

如何在 C# 中用单个空格替换多个空格?

c# - 可变关键字使用 vs 锁定

如何反序列化包含同一类嵌套的json类(Unity C#)?