2014-03-06 100 views
6

我創建在C#5.0 TCP服務器並調用tcpListener.AcceptTcpClientAsyncnetworkStream.ReadAsync異步/等待會影響tcp服務器的性能嗎?

然而,當,當我檢查我的進程資源管理器服務器的CPU使用率我使用的await關鍵字,我有以下結果:

TCP同步版本:10%的CPU使用率

TCP異步版本:30%的CPU使用率一半使用的是內核使用。此外,我通過在網絡流的外觀中添加計數器來測量我接收數據的次數,異步版本循環120,000次,而同步版本循環2,500,000次。

從接收到的消息來看,每秒接收3個不同客戶端的消息時,異步版本比同步版本慢15%。

爲什麼Async版本比Sync版本使用更多CPU?

這是因爲異步/等待關鍵字?

這是正常的,一個異步Tcp服務器比它的同步對手慢?

編輯:這裏是異步TCP服務器代碼

public class AsyncTcpListener : ITcpListener 
{ 
    private readonly ServerEndpoint _serverEndPoint; // Custom class to store IpAddress and Port 

    public bool IsRunning { get; private set; } 

    private readonly List<AsyncTcpClientConnection> _tcpClientConnections = new List<AsyncTcpClientConnection>(); 

    private TcpListener _tcpListener; 

    public AsyncTcpMetricListener() 
    { 
     _serverEndPoint = GetServerEndpoint(); 
    } 

    public async void Start() 
    { 
     IsRunning = true; 

     RunTcpListener(); 
    } 

    private void MessageArrived(byte[] buffer) 
    { 
     // Deserialize 
    } 

    private void RunTcpListener(){ 
     _tcpListener = null; 
     try 
     { 
      _tcpListener = new TcpListener(_serverEndPoint.IpAddress, _serverEndPoint.Port); 
      _tcpListener.Start(); 
      while (true) 
      { 
       var tcpClient = await _tcpListener.AcceptTcpClientAsync().ConfigureAwait(false); 
       var asyncTcpClientConnection = new AsyncTcpClientConnection(tcpClient, MessageArrived); 
       _tcpClientConnections.Add(asyncTcpClientConnection); 
      } 
     } 
     finally 
     { 
      if (_tcpListener != null) 
       _tcpListener.Stop(); 

      IsRunning = false; 
     } 
    } 

    public void Stop() 
    { 
     IsRunning = false; 
     _tcpListener.Stop(); 
     _tcpClientConnections.ForEach(c => c.Close()); 
    } 
} 

的例子對於每個新的客戶端,我們創建一個新的AsyncTcpConnection

public class AsyncTcpClientConnection 
{ 
    private readonly Action<byte[]> _messageArrived; 
    private readonly TcpClient _tcpClient; 

    public AsyncTcpClientConnection(TcpClient tcpClient, Action<byte[]> messageArrived) 
    { 
     _messageArrived = messageArrived; 
     _tcpClient = tcpClient; 
     ReceiveDataFromClientAsync(_tcpClient); 
    } 

    private async void ReceiveDataFromClientAsync(TcpClient tcpClient) 
    { 
     var readBuffer = new byte[2048]; 
     // PacketProtocol class comes from http://blog.stephencleary.com/2009/04/sample-code-length-prefix-message.html 
     var packetProtocol = new PacketProtocol(2048); 
     packetProtocol.MessageArrived += _messageArrived; 

     try 
     { 
      using (tcpClient) 
      using (var networkStream = tcpClient.GetStream()) 
      { 
       int readSize; 
       while ((readSize = await networkStream.ReadAsync(readBuffer, 0, readBuffer.Length).ConfigureAwait(false)) != 0) 
       { 
        packetProtocol.DataReceived(readBuffer, readSize); 
       } 
      } 
     } 
     catch (Exception ex) 
     { 
      // log 
     } 
    } 

    public void Close() 
    { 
     _tcpClient.Close(); 
    } 
} 

EDIT2:同步服務器

public class TcpListener : ITcpListener 
{ 
    private readonly ObserverEndpoint _serverEndPoint; 
    private readonly List<TcpClientConnection> _tcpClientConnections = new List<TcpClientConnection>(); 

    private Thread _listeningThread; 
    private TcpListener _tcpListener; 
    public bool IsRunning { get; private set; } 

    public TcpMetricListener() 
    { 
     _serverEndPoint = GetServerEndpoint(); 

    } 


    public void Start() 
    { 
     IsRunning = true; 
     _listeningThread = BackgroundThread.Start(RunTcpListener); 
    } 

    public void Stop() 
    { 
     IsRunning = false; 

     _tcpListener.Stop(); 
     _listeningThread.Join(); 
     _tcpClientConnections.ForEach(c => c.Close()); 
    } 

    private void MessageArrived(byte[] buffer) 
    { 
     // Deserialize 
    } 

    private void RunTcpListener() 
    { 
     _tcpListener = null; 
     try 
     { 
      _tcpListener = new TcpListener(_serverEndPoint.IpAddress, _serverEndPoint.Port); 
      _tcpListener.Start(); 
      while (true) 
      { 
       var tcpClient = _tcpListener.AcceptTcpClient(); 
       _tcpClientConnections.Add(new TcpClientConnection(tcpClient, MessageArrived)); 
      } 
     } 
     finally 
     { 
      if (_tcpListener != null) 
       _tcpListener.Stop(); 

      IsRunning = false; 
     } 
    } 
} 

和連接

public class TcpClientConnection 
{ 
    private readonly Action<byte[]> _messageArrived; 
    private readonly TcpClient _tcpClient; 
    private readonly Task _task; 
    public TcpClientConnection(TcpClient tcpClient, Action<byte[]> messageArrived) 
    { 
     _messageArrived = messageArrived; 
     _tcpClient = tcpClient; 
     _task = Task.Factory.StartNew(() => ReceiveDataFromClient(_tcpClient), TaskCreationOptions.LongRunning); 

    } 

    private void ReceiveDataFromClient(TcpClient tcpClient) 
    { 
     var readBuffer = new byte[2048]; 
     var packetProtocol = new PacketProtocol(2048); 
     packetProtocol.MessageArrived += _messageArrived; 


      using (tcpClient) 
      using (var networkStream = tcpClient.GetStream()) 
      { 
       int readSize; 
       while ((readSize = networkStream.Read(readBuffer, 0, readBuffer.Length)) != 0) 
       { 
        packetProtocol.DataReceived(readBuffer, readSize); 
       } 
      } 
    } 


    public void Close() 
    { 
     _tcpClient.Close(); 
     _task.Wait(); 
    } 
} 
+0

你並不是在'AsyncTcpClientConnection'內調用'ReceiveDataFromClientAsync'。雖然與性能無關,但仍然是一個錯誤。 –

+1

我無法等待'ReceiveDataFromClientAsync',因爲程序會一直等待,不會聽另一個tcp客戶端 – alexandrekow

+0

看看這是否有幫助http://msdn.microsoft.com/en-us/magazine/dn605876.aspx –

回答

0

我有一個async也是問題,這是我的發現:https://stackoverflow.com/a/22222578/307976

另外,我有使用async例如here可以擴展異步TCP服務器/客戶端好。

+0

感謝分享你的見解。你的實現很好。我喜歡你處理服務器乾淨的關閉的方式。我使用服務器實現的一些代碼重構了偵聽器。但性能仍然相同,CPU峯值相同。 – alexandrekow

+0

您是否檢查過那些CPU尖峯不是GC清潔垃圾?打開性能收集器爲您的服務器應用程序實例添加「GC時間百分比」計數器,並檢查尖峯是否與您提到的尖峯一致。我有這樣的感覺,即異步代碼讓GC生活變得困難。 – vtortola

+0

我的算法在GC中的時間少於1%。 – alexandrekow

0

請嘗試以下實施ReceiveInt32AsyncReceiveDataAsync直接接收,而不是使用tcpClient.GetStreamnetworkStream.ReadAsync您的長度爲前綴的消息:

public static class SocketsExt 
{ 
    static public async Task<Int32> ReceiveInt32Async(
     this TcpClient tcpClient) 
    { 
     var data = new byte[sizeof(Int32)]; 
     await tcpClient.ReceiveDataAsync(data).ConfigureAwait(false); 
     return BitConverter.ToInt32(data, 0); 
    } 

    static public Task ReceiveDataAsync(
     this TcpClient tcpClient, 
     byte[] buffer) 
    { 
     return Task.Factory.FromAsync(
      (asyncCallback, state) => 
       tcpClient.Client.BeginReceive(buffer, 0, buffer.Length, 
        SocketFlags.None, asyncCallback, state), 
      (asyncResult) => 
       tcpClient.Client.EndReceive(asyncResult), 
      null); 
    } 
} 

看看這給任何改進。在附註中,我也建議使ReceiveDataFromClientAsyncasync Task方法並將Task存儲在AsyncTcpClientConnection(用於狀態和錯誤跟蹤)中。

+0

我試過你的實現,它導致兩個問題:1)它慢得多(4k消息/秒而不是220k消息/秒)。 2)它沒有考慮到我們可以在TCP中接收不完整的數據包的事實。不過這是非常優雅的代碼。 – alexandrekow

+0

@alexandrekow,1)我認爲值得嘗試:),2)我認爲這就是消息長度前綴的用途。理論上,這樣的異步接收操作在接收到所有請求的數據之前不會完成;即對於'Int32',只要接收到4個字節,就完成'buff'] - 'buff.Length'字節。 – Noseratio

+0

@alexandrekow,更多的步驟是嘗試增加['Socket.ReceiveBufferSize'](http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.receivebuffersize(v = vs.110)的.aspx) – Noseratio