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

2 回答 2

1

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

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

在完全崩溃服务器方面,只需继续测试,并记录任何全局未处理的异常。这样,您将了解可以预期的一切。

于 2011-01-27T21:42:05.343 回答
1

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

于 2011-01-27T21:43:47.357 回答