簡體   English   中英

C#偵聽器線程上的CPU使用率高,睡眠未命中斷開連接

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

我的連接處理程序在下面(這比個人生產代碼更多用於個人實驗)

如果我不在while循環中的任何地方添加Thread.Sleep,它將開始占用CPU。相反,如果我執行Sleep來緩解無休止的while-spam,則會錯過斷開連接的時間。取決於正在運行的客戶端/線程的數量,所以不是引起監聽器本身高使用率的原因,是下面發布的實際客戶端線程。.有人對如何解決此問題有任何想法嗎?

(我避免使用基於等待的解決方案,因為我對async / await不夠熟悉,並且線程化方法對於這個相當小的項目運行良好)

我只是在SO周圍進行了短暫搜索,尋找解決方案,卻沒有發現任何特定問題或提供解決方案,只是指示人們異步/等待文章,因此,如果我沒有找到適用的答案,對不起。

        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('\0');
                        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. 定期寫。 需要執行這些寫操作以確保連接仍然可行

正確的線程化方法每個連接需要兩個線程。 我不認為這比異步方法更簡單。

PS:如果您的代碼使用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('\0', 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.");
    }

升級我使用EndRead來確保所有請求均已到達。

通過其他方式,您可以使用BeginWriteEndWrite知道套接字何時完成寫操作以關閉連接

這樣,您將獲得連續不斷的聯系,並盡快獲得聯系。 就我而言,我將CPU使用率從30%降低到0%,每小時的請求量為15,000。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM