C# 高 CPU 侦听器线程使用率,休眠未命中断开连接

C# High CPU usage on Listener thread, sleeping misses disconnect

我的连接处理程序在下面(这更多是为了个人实验而不是生产代码)

如果我不在 while 循环中的任何地方添加 Thread.Sleep,它就会开始下降 CPU.. 相反,如果我睡眠以减轻无休止的 while-spam,我会错过断开连接.. CPU 的上升与 clients/threads 运行 的数量成正比,因此导致高使用率的不是侦听器本身,而是下面发布的实际客户端线程。 .有人知道如何解决这个问题吗?

(我正在避免基于等待的解决方案,因为我对 async/await 不够熟悉并且线程方法对于这个相当小的项目工作正常)

我只是简单地搜索了 SO 以寻找解决方案,并没有注意到任何这个特定问题或提供了除了指导人们阅读 async/await 文章之外的解决方案,如果我确实错过了一个适用的答案。

        private void HandleConnection(CancellationToken ct) {
        int recv = 0;
        byte[] buf = new byte[4096];
        Trace.WriteLine($"{_name} Connected");
        if (_ns.CanWrite && _client.Connected) {
            _ns.Write(Encoding.BigEndianUnicode.GetBytes("■WEL"), 0, Encoding.BigEndianUnicode.GetBytes("■WEL").Length);
            try {
                while (_client.Connected && !ct.IsCancellationRequested) {

                    while (!_ns.DataAvailable) { //first attempted solution
                        Thread.Sleep(100); // miss discon if i sleep here
                        }

                    if (ct.IsCancellationRequested) {
                        Trace.WriteLine($"{(string)this} thread aborting");
                        break;
                        }

                    buf = new byte[4096];

                    if (_client.Connected && _ns.DataAvailable) {

                        recv = _ns.Read(buf, 0, buf.Length);
                        } else {
                        recv = 0;
                        }

                    if (recv > 0) {

                        string r = Encoding.BigEndianUnicode.GetString(buf);
                        r = r.TrimEnd('[=11=]');
                        if (String.IsNullOrEmpty(r) || String.IsNullOrWhiteSpace(r))
                            r = null; //need the !not version
                        else
                            if (ParseMsg(r))
                                break;
                        }

                    //Thread.Sleep(100); // also miss discon here too

                    }
                } catch (IOException ioe) { }
            Trace.WriteLine($"{_name} Disconnected");
            if (OnDisconnected != null)
                OnDisconnected(this);
            }
        }

通过套接字进行通信的正确方法是:

  1. 继续阅读。这些读取将阻塞,直到数据进入或直到套接字正常断开连接(可通过读取完成的 0 字节检测到)。
  2. 定期写。 These writes are required to ensure the connection is still viable.

正确的线程方法需要每个连接 两个 个线程。我不相信它比异步方法更简单。

P.S。如果您的代码使用 Connected,那么它有一个错误。正确的解决方案永远不需要使用 Connected.

我遇到了和你一样的问题,但我发现解决这个问题的最好方法是:

不使用睡眠和线程阻塞套接字。

升级: 如果您使用线程并进入服务器休眠,则每个连接接收和回复每条消息的性能会很低。

如果你想要一个高性能的应用程序,你不能为你接受的每个连接使用休眠或创建线程。最好的方法是使用 NetworkStream 提供的 Asyncronous 方法,使用 BeginReadEndRead,例如:

    public void run()
    {
        server = new TcpListener(IPAddress.Any, port);
        server.Start();

        log.Info("Starting SocketServer on Port [" + port + "]");

        while (keepRunning)
        {
            try
            {
                TcpClient socket = server.AcceptTcpClient();
                if (keepRunning)
                    RequestManager.createRequestForEvalue(socket, idLayout);
            }
            catch (Exception ex)
            {
                log.Error(ex.Message);
                log.Error(ex.StackTrace);
            }
        }

        log.Info("Server Stoped.");
    }

    public static bool createRequestForEvalue(TcpClient socket, int idLayout)
    {
        Request req = null;
        req = new Request(socket,idLayout);

        registerRequest(req.ID,req); //Registra el Request, para su posterior uso.

        // DO NOT CREATE THREADS FOR ATTEND A NEW CONNECTION!!!
        //Task.Factory.StartNew(req.RunForIVR);
        //ThreadPool.QueueUserWorkItem(req.RunForIVR);

        req.startReceiveAsync(); //Recive data in asyncronus way.
        return true;
    }

    public void startReceiveAsync()
    {
        try
        {
            log.Info("[" + id + "] Starting to read the Request.");
            requestBuffer = new byte[BUFFER_SIZE];
            NetworkStream nst = socket.GetStream();
            nst.BeginRead(requestBuffer, 0,BUFFER_SIZE, this.requestReceived, nst);
        }catch(Exception ex)
        {
            log.Error("[" + id + "] There was a problem to read the Request: " + ex.Message);
            RequestManager.removeRequest(id);
            closeSocket();
        }
    }

    public void requestReceived(IAsyncResult ar)
    {

        try
        {   
        NetworkStream nst = socket.GetStream();
        int bread = nst.EndRead(ar); //Block the socket until all the buffer has been available.
        message = Encoding.UTF8.GetString(requestBuffer, 0, BUFFER_SIZE);
            log.Info("[" + id + "] Request recived: [" + message +"]");
            RunForIVR();
        }
        catch (Exception ex)
        {
            log.Error("[" + id + "] There was a problem to read the Request: " + ex.Message);
            RequestManager.removeRequest(id);
            closeSocket();
        }

    }

    public void SendResponse(String Response)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(Response);
        sb.Append('[=10=]', BUFFER_SIZE - Response.Length);
        string message = sb.ToString();

        log.Info("[" + id + "] ivrTrans CMD: [" + idCMD + "] RESPONSE: [" + Response + "]");

        NetworkStream nst = socket.GetStream();
        byte[] buffer = new byte[BUFFER_SIZE];
        for (int i = 0; i < BUFFER_SIZE; i++)
            buffer[i] = (byte)message.ElementAt(i);

        nst.BeginWrite(buffer, 0, BUFFER_SIZE, this.closeSocket, nst);
    }

    public void closeSocket(IAsyncResult ar = null)
    {

        try
        {
            if (ar != null) //Since 4.24
            {
                NetworkStream nst = socket.GetStream();
                nst.EndWrite(ar);
            }

            socket.Close();
            socket = null;
        }catch(Exception ex)
        {
            log.Warn("[" + id + "] There was a problem to close the socket. Error: " + ex.Message + Environment.NewLine + ex.StackTrace);
        }
        log.Info("[" + id + "] Socket closed.");
    }

Upgrade I use the EndRead to be sure that the request has been arrived at all.

通过其他方式,您可以使用 BeginWriteEndWrite 知道套接字何时完成写入以关闭连接

通过这种方式,您将以持续的方式尽快参与连接。在我的例子中,我将 CPU 使用率从 30% 减少到 0%,每小时请求数量为 15K。