【问题标题】:Does async/await affects tcp server performance?async/await 会影响 tcp 服务器性能吗?
【发布时间】:2014-04-08 23:21:56
【问题描述】:

我在 C# 5.0 中创建 Tcp 服务器,并且在调用 tcpListener.AcceptTcpClientAsyncnetworkStream.ReadAsync 时使用 await 关键字

但是,当我使用 Process Explorer 检查服务器的 CPU 使用率时,我得到以下结果:

Tcp Sync 版本:10% CPU 使用率

Tcp Async 版本:30% CPU 使用率一半的使用率是内核使用率。

此外,我通过在网络流的 while 外观中添加一个计数器来测量我接收数据的时间,异步版本循环 120,000 次,而同步版本循环 2,500,000 次。

在接收来自 3 个不同客户端的消息时,异步版本比同步版本慢 15%。

为什么异步版本比同步版本使用更多的 CPU?

这是因为 async/await 关键字吗?

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

【问题讨论】:

  • 您不会在AsyncTcpClientConnection 中等待对ReceiveDataFromClientAsync 的调用。虽然与性能无关,但它仍然是一个错误。
  • 我不能等待ReceiveDataFromClientAsync,因为程序会永远等待并且永远不会监听另一个 tcp 客户端

标签: c# tcp async-await


【解决方案1】:

我对@9​​87654323@ 也有疑问,这些是我的发现:https://stackoverflow.com/a/22222578/307976

另外,我有一个异步 TCP 服务器/客户端,使用 async 示例 here,可以很好地扩展。

【讨论】:

  • 感谢您分享您的见解。你的实现很好。我喜欢你处理服务器干净关闭的方式。我使用您的服务器实现的一些代码重构了我的侦听器。但是性能仍然相同,CPU 峰值相同。
  • 你检查过那些 CPU 峰值不是 GC 清理垃圾吗?打开性能收集器,为您的服务器应用程序实例添加“% of time in GC”计数器,并检查峰值是否与您提到的峰值一致。我觉得异步代码让 GC 很难过。
  • 我的算法在 GC 中的时间不到 1%。
【解决方案2】:

尝试使用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);
    }
}

看看这是否有任何改进。另外,我还建议将ReceiveDataFromClientAsync 设为async Task 方法并将其返回的Task 存储在AsyncTcpClientConnection 中(用于状态和错误跟踪)。

【讨论】:

  • 我尝试了您的实现,但它导致了两个问题:1)速度慢很多(4k 消息/秒而不是 220k 消息/秒)。 2)它没有考虑到我们可以在TCP中接收到不完整的数据包这一事实。然而,这是非常优雅的代码。
  • @alexandrekow,1)我认为值得尝试 :),2)我认为这就是消息长度前缀的用途。理论上,像这样的异步接收操作在收到所有请求的数据之前是完成的;即,对于Int32,它在收到 4 个字节后立即完成,对于 buff[] - buff.Length 字节。
  • @alexandrekow,还有一步是尝试增加Socket.ReceiveBufferSize
猜你喜欢
  • 2011-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-29
  • 2012-02-04
  • 2011-01-28
  • 1970-01-01
相关资源
最近更新 更多