问题描述
我在 C# 5.0 中创建了一个 Tcp 服务器,我在调用 tcpListener.AcceptTcpClientAsync
和 networkStream.ReadAsync
I am creating a Tcp server in C# 5.0 and I am using the await keyword when calling tcpListener.AcceptTcpClientAsync
and networkStream.ReadAsync
但是,当我使用 Process Explorer 检查服务器的 CPU 使用率时,我得到以下结果:
However when I check the CPU usage of my server with Process Explorer I have the following results:
Tcp 同步版本:10% CPU 使用率
Tcp 异步版本:30% CPU 使用率一半的使用量是内核使用率.
Tcp Async Version: 30% CPU usage Half of the usage is kernel usage.
此外,我通过在网络流的 while 外观中添加一个计数器来测量我收到数据的次数,异步版本循环 120,000 次,而同步版本循环 2,500,000 次.
Moreover, I measured how many time I received data by adding a counter inside the while look of the network stream, and the async version loops 120,000 times while the sync version loops 2,500,000 times.
就接收到的消息/秒而言,当接收来自 3 个不同客户端的消息时,异步版本比同步版本慢 15%.
In term of message received/second the async version is 15% slower than the sync version when receiving messages from 3 different clients.
为什么异步版本比同步版本使用更多的 CPU?
这是因为 async/await 关键字吗?
Is this because of the async/await keyword ?
异步 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
For each new client we create a new 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();
}
}
同步服务器
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();
}
}
推荐答案
尝试以下 ReceiveInt32Async
和 ReceiveDataAsync
的实现来直接接收你的长度前缀消息,而不是使用 tcpClient.GetStream
和 networkStream.ReadAsync
:
Try the following implementation of ReceiveInt32Async
and ReceiveDataAsync
for receiving your length-prefixed messages directly, instead of using tcpClient.GetStream
and 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);
}
}
看看这是否有任何改进.附带说明一下,我还建议将 ReceiveDataFromClientAsync
设为 async Task
方法并将它返回的 Task
存储在 AsyncTcpClientConnection
中(用于状态和错误跟踪).
See if this gives any improvements. On a side note, I also suggest making ReceiveDataFromClientAsync
an async Task
method and storing the Task
it returns inside AsyncTcpClientConnection
(for status and error tracking).
这篇关于async/await 会影响 tcp 服务器的性能吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!