2014-03-06 64 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