C# 异步 TCP 服务器是否过度设计?

5
这真的是一个实现问题,所以我觉得最好从我的具体情况开始。我有一个C#服务器,可以异步地从移动客户端接收TCP连接。当移动客户端连接时,会启动一个新线程,客户端发送一个小的(通常<100字节)文本消息并接收一个大小相似的回复。服务器响应后,关闭连接并结束线程。
目前的基本用法是用户登录后,检查一些东西,有时长达5分钟,发送小消息,从而在服务器上快速创建新线程,然后断开连接,几个小时后再重新连接。此外,每个用户都有自己的服务器运行在他们的PC上,因此大多数服务器在任何给定时间只会有一个客户端连接,在极少数情况下可能有两个。
现在我遇到了以下错误:远程主机强制关闭了一个现有的连接,这让我想,我做错了吗? 那么我的问题是:
  1. 我的当前设置是否适用于这里?
  2. 如果是这样,我应该在发送小消息后结束线程还是保持其活动并在空闲一段时间后关闭?
  3. 万一我做得完全正确,很不可能,我应该通过简单地重试几次来避免错误吗?
  4. 最后,如果出现错误,服务器会完全停止(服务器由另一个进程生成,任何未捕获的异常都会使其停止),如果我们到达了这一步,并且我的实现是正确的,我该如何避免这种情况?

编辑:

回答一些问题:

  • 异常发生在我接收所有数据之前,但仅在用户快速连续发送多条消息时发生。
  • 据我所知,最大积压量为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());    
        }
    }

3
这句话听起来像是一个UDP应用程序——你可能在进行三次TCP握手的时间和带宽方面花费了更多,而不是处理消息。 - Ben Voigt
你可能是对的,如果我没记错的话,我当时不能使用UDP,但我会再次研究它。 - zkwentz
2个回答

1
理想情况下,您应该使用.NET线程池,这比为每个连接创建一个新线程要高效得多。如果您正在使用TCPListener上的现有异步模式,请分享您的确切“异步”代码 - 您可能已经在使用线程池。
关于异常,当客户端从服务器断开连接时,您会看到这种情况。它是否发生在您成功接收所有数据之前?您是否在客户端端刷新套接字?
至于完全崩溃服务器,只需继续测试,并记录任何全局未处理的异常。这样,您就可以了解到可以预期的所有内容。

使用 WSAAsyncSelectWSAEventSelect 比使用线程池更有效率。 - Ben Voigt
我已经添加了我的代码,异常发生在接收到所有数据之前,因此很可能与最大连接数有关,因为使用Async时它们都会立即被接受但不会被处理。 - zkwentz

1

你可能想看一下这篇文章,它列出了几个需要检查的好东西。例如,当你在socket上调用.Listen()时,你的backlog设置是多少?


我之前没有设置它,现在我明确地设置了它,错误的实例减少了,但仍然存在。我认为最大值是5,除非你正在运行Windows Server。不过我会看一下这篇文章,看起来非常有用。 - zkwentz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接