2014-03-06 5 views
6

Ich bin ein TCP-Server in C# 5.0 Erstellen und ich das await Schlüsselwort bin mit, wenn jedoch tcpListener.AcceptTcpClientAsync und networkStream.ReadAsyncBeeinflusst async/await die tcp-Serverleistung?

aufrufen, wenn ich die CPU-Auslastung von meinem Server mit Process Explorer überprüfen habe ich die folgenden Ergebnisse:

tcp Sync Version: 10% CPU-Auslastung

tcp Async Version: 30% CPU-Auslastung die Hälfte der Nutzungs Kernel Nutzung ist.

Darüber hinaus habe ich gemessen, wie viel Zeit ich Daten durch Hinzufügen eines Zählers innerhalb der while-Look des Netzwerk-Stream erhalten, und die asynchrone Version Schleifen 120.000 Mal, während die Sync-Version 2.500.000 Schleifen.

In Bezug auf die empfangene Nachricht/Sekunde ist die Async-Version um 15% langsamer als die Synchronisierungsversion, wenn Nachrichten von 3 verschiedenen Clients empfangen werden.

Warum verwendet die Async-Version viel mehr CPU als die Sync-Version?

Liegt das an dem Schlüsselwort async/await?

Ist das normal, dass ein Async-Tcp-Server langsamer als sein Synchron-Gegenstück ist?

EDIT: Hier ist ein Beispiel für die Asynchron-tcp-Server-Code

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

Für jeden neuen Kunden haben wir ein neues 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 erstellen: Synchron-Server

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; 
     } 
    } 
} 

Und die Verbindung

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

Sie haben keinen Aufruf von 'ReceiveDataFromClientAsync' in' AsyncTcpClientConnection'. Obwohl es nicht mit der Leistung zusammenhängt, ist es immer noch ein Fehler. –

+1

Ich kann 'ReceiveDataFromClientAsync' nicht erwarten, weil das Programm ewig warten würde und niemals auf einen anderen TCP-Client warten würde. – alexandrekow

+0

Sehen Sie, ob dies hilft http://msdn.microsoft.com/en-us/magazine/dn605876.aspx –

Antwort

0

ich mit einem async auch Probleme haben und diese sind meine Ergebnisse: https://stackoverflow.com/a/22222578/307976

Auch ich habe einen asynchronen TCP Server/Client mit async Beispiel here, die skaliert Gut.

+0

Vielen Dank für die Freigabe deine Einsicht. Ihre Implementierung ist nett. Ich mag die Art, wie Sie mit einem sauberen Schließen des Servers umgehen. Ich habe meinen Listener mithilfe von Code der Serverimplementierung neu strukturiert. Die Leistung ist jedoch immer noch die gleiche, mit den gleichen CPU-Spitzen. – alexandrekow

+0

Haben Sie überprüft, dass diese CPU-Spitzen nicht die GC-Reinigung sind? Öffnen Sie den Leistungskollektor, fügen Sie den Zähler "% of time in GC" für Ihre Serveranwendungsinstanz hinzu und überprüfen Sie, ob die Spitzen mit den von Ihnen genannten Spitzen übereinstimmen. Ich habe das Gefühl, dass asynchroner Code dem GC das Leben schwer macht. – vtortola

+0

Weniger als 1% Zeit in GC für meinen Algorithmus. – alexandrekow

0

Versuchen Sie, die folgende Implementierung von ReceiveInt32Async und ReceiveDataAsync für Ihre Länge-Präfix-Nachrichten direkt empfangen, statt mit tcpClient.GetStream und networkStream.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); 
    } 
} 

sehen, ob dies irgendwelche Verbesserungen gibt. Nebenbei schlage ich auch vor, ReceiveDataFromClientAsync eine async Task Methode zu machen und die Task speichert sie innerhalb AsyncTcpClientConnection (für Status-und Fehlerverfolgung) zurück.

+0

Ich habe versucht Ihre Implementierung und es verursacht zwei Probleme: 1) Es ist viel langsamer (4k Nachrichten/Sek anstelle von 220k Nachrichten/Sek). 2) Es berücksichtigt nicht die Tatsache, dass wir unvollständige Pakete in TCP empfangen können. Es ist jedoch sehr eleganter Code. – alexandrekow

+0

@alexandrekow, 1) Ich dachte, es wäre wert, es zu versuchen :), 2) Ich dachte, das ist, wofür das Präfix der Länge der Nachricht war. Theoretisch ist eine asynchrone Empfangsoperation wie diese * nicht * abgeschlossen, bis * alle * angeforderten Daten empfangen wurden; d.h., für "Int32" schließt es ab, sobald 4 Bytes empfangen wurden, für "buff []' - "buff.Length" Bytes. – Noseratio

+0

@alexandrekow, ein weiterer Schritt wäre, dies mit einer erhöhten [Socket.ReceiveBufferSize'] (http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.receivebuffersize (v = vs.110) .aspx) – Noseratio

Verwandte Themen