2017-04-10 87 views
1

我的連接處理器的下方(這是比較個人的實驗不是生產代碼)C#的高CPU使用率,睡眠缺失斷開

如果我不while循環的任意位置添加一個Thread.sleep代碼,它開始吸收CPU。相反,如果我通過睡眠來緩解無盡的垃圾郵件,我錯過了斷開連接.CPU與客戶端/線程運行數量成正比,因此它不是偵聽器本身導致高使用率,這是實際的客戶端線程下面張貼..任何人有任何想法如何解決這個問題?

(我回避的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

你應該考慮使用'async/await'變種 – MickyD

+0

你是什麼意思你錯過了斷開?你是說在斷開連接後客戶端仍然連接,並且你被困在第一個'while'循環中? – TyCobb

+0

@TyCobb是的 - 我的意思是丟失斷開連接是遠程連接發送「DSC」並且丟棄,然後ParseMsg(r)看到並正常關閉連接。但是如果上面的while-thread有一個Sleep() ,它從來沒有收到信息。而且,即使在遠程客戶端丟失後,_client.Connected也不會切換爲false。 – MisterNad

回答

1

我有同樣的problema比你,但我發現,解決這個問題的最好辦法是:

不阻止與睡覺和線程的插座。

升級如果你使用線程,睡到服務器,它會遭受低性能接收並通過每個連接回答每一個消息。

如果你想要一個高性能的應用程序,你不能使用睡眠或爲你接受的每個連接創建線程。最好的辦法是使用Asyncronous中的方法的NetworkStream提供,使用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%,每小時15K的請求量。

+0

*「在這個示例中,我不使用'EndRead',因爲我不需要」* - 您怎麼知道有多少字節被讀取或者是否有異常? – acelent

+0

@acelent我的協議定義爲每個請求和響應設置了612字節的大小。當你使用套接字並且你是協議所有者的時候,建立一個協議頭是一個很好的習慣,這個協議頭有一定數量的數據要接收,或者在這種情況下,設置一個唯一的數據量來發送和接收我不是這個協議的擁有者)。 –

+0

如果您的頭部具有數據大小,您可以調用兩次'BegineRead'。首先獲取Header:'startReceiveAsync',它調用'nst.BeginRead(requestBuffer,0,HEADER_SIZE,this.headerReceived,nst);',第二步,進入方法'headerReceived'獲取數據量並請求保留字節'nst.BeginRead(requestBuffer,0,DataSize,this.requestReceived,nst);'其中'DataSize'是您從頭部獲得的數據量。 –

3

在一個套接字通信的正確方法是:

  1. 連續讀取。這些讀取將阻塞,直到數據進入或直到套接字正常斷開連接(通過完成0字節讀取的讀取可檢測到)。
  2. 定期寫。 These writes are required to ensure the connection is still viable

一個適當的線程方法需要每個連接有兩個兩個線程。我不相信它比異步方法更簡單。

P.S.如果你的代碼使用Connected,那麼它有一個錯誤。合適的解決方案永遠不需要使用Connected

+0

欣賞響應時間,甚至簡要查看您的文章,您的回答更多的是「您錯了」,而不是「通過線程來處理事情的正確方式的示例或資源」。 – MisterNad

+4

@MisterNad:沒有使用線程方法的示例或資源,因爲它本質上是不可縮放的。正如我的博客文章系列中提到的,最好的整體資源是史蒂文斯的* TCP/IP Illustrated *。 –