C# 异步 TCP 服务器杀伤力过大?

发布于 2024-10-14 23:02:31 字数 5725 浏览 1 评论 0原文

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

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

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

现在我遇到了以下错误,现有连接被远程主机强制关闭,这让我思考,我做错了吗?

我的问题:

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

编辑:

为了回答这里的一些问题:

  • 异常发生在我收到所有数据之前,但仅在用户快速连续发送多条消息的情况下发生。
  • 据我所知,除非用户运行 Windows Server,否则最大积压是 5,但是我没有设置我的,并且我不知道默认值是什么,我将尝试将其显式设置为 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());    
        }
    }

This is really an implementation question so I feel it's best to start with my specific case.

I've got a C# server that listens for TCP connections asynchronously from mobile clients. When a mobile client connects a new thread is started, the client sends a little (<100 bytes usually) text message and receives one of similar size back. After the server responds, it closes the connection and ends the thread.

Current basic usage is a user logs in, checks on stuff for sometimes up to 5 minutes, sending little messages and thus creating new threads on the server in rapid succession, and they disconnect only to reconnect a few hours later. Also, every user has their own server they run on their PC, and as such most servers will only ever have one client connected at any given time, in RARE cases two.

Right now I'm running into the following error, An existing connection was forcibly closed by the remote host, and it has got me thinking, am I doing this wrong?

So my question(s):

  1. Is my current setup appropriate here?
  2. If so, should I be ending the thread after a little message is sent or keeping it alive and closing after a given period of idle?
  3. On the off chance that I'm doing everything correctly, highly unlikely, should I be avoiding the error by simply retrying a few times before giving up?
  4. Fourth and finally, that error completely kills the server (the server is spawned by another process and any untrapped exception kills it), if we've made it this far, and my implementation is OK, how can I avoid that?

EDIT:

In response to some of the questions here:

  • The exception is occurring before I receive all the data, but only in instances where a user has sent multiple messages in quick succession.
  • From what I recall max backlog is 5 unless a user is running Windows Server, however I have not set mine and I don't know what the default is, I'll try explicitly setting it to 5.

Async Server Code:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

相对绾红妆 2024-10-21 23:02:31

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

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

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

Ideally, you'd be using the .NET threadpool, which would be much more efficient than creating a new thread for every connection. Can you please share your exact "async" code - if you're using the existing async pattern on TCPListener then you're probably already using the threadpool.

With respect to the exception, that's what you'd expect to see when your clients disconnect from the server. Is it occurring before you manage to receive all the data? Are you flushing your socket on the client side?

In terms of completely crashing the server, just keep testing, and log any globally unhandled exceptions. That way you'll learn about everything that can be expected.

兰花执着 2024-10-21 23:02:31

您可能想看看这篇文章,其中列出了一些需要检查的事项。例如,当您在套接字上 .Listen() 时,您的待办事项设置为多少?

You might want to have a look at this article, which has a good list of several things to check. For example, what is your backlog set to when you .Listen() on your socket?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文