使用SocketAsyncEventArgs和它的异步方法作为Windows Service开发了一个TCP Server。我在Main的开头有这两行代码:
ThreadPool.SetMaxThreads(15000, 30000);
ThreadPool.SetMinThreads(10000, 20000);并且两者都返回true (返回值被记录)。现在,2000到3000个客户端开始向这个服务器发送消息,它开始接受连接(我计算了连接的数量,结果和预期的一样--有一个连接池)。服务器进程的线程计数将增长到约2050到约3050。到目前一切尚好!
现在有了一个Received方法,它将在ReceiveAsync返回true或由SocketAsyncEventArgs的Completed事件调用。
现在问题开始了:不管客户端连接了多少以及它们发送了多少消息,Received在一秒钟内最多会被调用20次!随着客户端数量的增加,这个数字(20)下降到~10。
环境:在同一台机器上模拟TCP服务器和客户端。我已经在两台机器上测试了代码,一台机器有2核CPU和4 4GB,另一台机器有8核CPU和12 4GB。没有数据丢失(还没有),有时我在每次接收操作中都会收到一条以上的消息。这很好。但是如何才能增加接收操作的数量呢?
关于实现的附加说明:代码很大,并且包含许多不同的逻辑。总体描述是:我有一个接受新连接的SocketAsyncEventArgs。它工作得很好。现在,对于每个新接受的连接,我创建了一个用于接收数据的新SocketAsyncEventArgs。我将这个(为接收创建的SocketAsyncEventArgs )放入一个池中。它不会被重用,但它的UserToken正用于跟踪连接;例如,那些断开连接的连接或7分钟内没有发送任何数据的连接将被关闭和释放( SocketAsyncEventArgs的AcceptSocket将被关闭(两者)、关闭和释放,SocketAsyncEventArgs对象本身也是如此)。下面是一个Sudo类,它执行这些任务,但删除了所有其他逻辑、日志记录和错误检查,以使其简单明了(这样可能更容易发现有问题的代码):
class Sudo
{
Socket _listener;
int _port = 8797;
public Sudo()
{
var ipEndPoint = new IPEndPoint(IPAddress.Any, _port);
_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listener.Bind(ipEndPoint);
_listener.Listen(100);
Accept(null);
}
void Accept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += AcceptCompleted;
}
else acceptEventArg.AcceptSocket = null;
bool willRaiseEvent = _listener.AcceptAsync(acceptEventArg); ;
if (!willRaiseEvent) Accepted(acceptEventArg);
}
void AcceptCompleted(object sender, SocketAsyncEventArgs e)
{
Accepted(e);
}
void Accepted(SocketAsyncEventArgs e)
{
var acceptSocket = e.AcceptSocket;
var readEventArgs = CreateArg(acceptSocket);
var willRaiseEvent = acceptSocket.ReceiveAsync(readEventArgs);
Accept(e);
if (!willRaiseEvent) Received(readEventArgs);
}
SocketAsyncEventArgs CreateArg(Socket acceptSocket)
{
var arg = new SocketAsyncEventArgs();
arg.Completed += IOCompleted;
var buffer = new byte[64 * 1024];
arg.SetBuffer(buffer, 0, buffer.Length);
arg.AcceptSocket = acceptSocket;
arg.SocketFlags = SocketFlags.None;
return arg;
}
void IOCompleted(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
Received(e);
break;
default: break;
}
}
void Received(SocketAsyncEventArgs e)
{
if (e.SocketError != SocketError.Success || e.BytesTransferred == 0 || e.Buffer == null || e.Buffer.Length == 0)
{
// Kill(e);
return;
}
var bytesList = new List<byte>();
for (var i = 0; i < e.BytesTransferred; i++) bytesList.Add(e.Buffer[i]);
var bytes = bytesList.ToArray();
Process(bytes);
ReceiveRest(e);
Perf.IncOp();
}
void ReceiveRest(SocketAsyncEventArgs e)
{
e.SocketFlags = SocketFlags.None;
for (int i = 0; i < e.Buffer.Length; i++) e.Buffer[i] = 0;
e.SetBuffer(0, e.Buffer.Length);
var willRaiseEvent = e.AcceptSocket.ReceiveAsync(e);
if (!willRaiseEvent) Received(e);
}
void Process(byte[] bytes) { }
}发布于 2012-12-27 03:45:58
它变慢的原因是因为这些线程中的每一个都需要切换上下文,而这是一个相对昂贵的操作。添加的线程越多,CPU中花费在上下文切换而不是实际代码上的百分比就越大。
您已经以一种相当奇怪的方式遇到了这种每个客户端一个线程的瓶颈。服务器端异步的全部意义在于减少线程的数量--不是每个客户端有一个线程,而是理想情况下系统中的每个逻辑处理器只有一个或两个线程。
你发布的异步代码看起来很好,所以我只能猜测你的Process方法中有一些容易忽略的非异步,阻塞的I/O,即数据库或文件访问。当I/O阻塞时,.NET线程池会检测到这一点,并自动创建一个新线程--在这里,它基本上失去了控制,Process中的I/O成为瓶颈。
异步管道真的需要100%异步才能从中获得任何显着的好处。半入会让你编写复杂的代码,其性能和简单的同步代码一样差。
如果您绝对不能使Process方法完全异步,那么您可能会有一些运气来伪造它。让事物在队列中等待,由一个小的、大小有限的线程池进行处理。
https://stackoverflow.com/questions/14044852
复制相似问题