gpt4 book ai didi

c# - 使用 SocketAsyncEventArgs 的服务器设计

转载 作者:太空狗 更新时间:2023-10-29 20:12:09 28 4
gpt4 key购买 nike

我想使用 SocketAsyncEventArgs 事件创建一个异步套接字服务器。

服务器应该同时管理大约 1000 个连接。处理每个数据包逻辑的最佳方式是什么?

服务器设计基于this MSDN example ,所以每个套接字都会有自己的 SocketAsyncEventArgs 来接收数据。

  1. 在接收函数中执行逻辑操作。不会产生任何开销,但由于下一个 ReceiveAsync() 调用不会在逻辑完成之前完成,因此无法从套接字读取新数据。我的两个主要问题是:如果客户端发送大量数据并且逻辑处理繁重,系统将如何处理它(因为缓冲区已满而丢失数据包)?另外,如果所有客户端同时发送数据,是否会有1000个线程,或者有内部限制,新线程不能在另一个线程执行完成之前启动?

  2. 使用队列。接收函数将非常短并且执行速度很快,但是由于队列的存在,你会有相当大的开销。问题是,如果你的工作线程在服务器负载很重的情况下不够快,你的队列可能会变满,所以你可能不得不强制丢弃数据包。您还会遇到生产者/消费者问题,这可能会降低整个队列的速度,因为有很多锁。

那么什么是更好的设计、接收函数中的逻辑、​​工作线程中的逻辑或我迄今为止错过的任何完全不同的东西。

关于数据发送的另一个任务。

将 SocketAsyncEventArgs 绑定(bind)到套接字(模拟接收事件)并使用缓冲系统对几个小数据包进行一次发送调用是否更好(假设数据包有时会!一个接一个地直接发送) 或者对每个数据包使用不同的 SocketAsyncEventArgs 并将它们存储在池中以重用它们?

最佳答案

要有效地实现异步套接字,每个套接字将需要超过 1 个 SocketAsyncEventArgs。每个 SocketAsyncEventArgs 中的 byte[] 缓冲区也存在问题。简而言之,只要发生托管 - native 转换(发送/接收),字节缓冲区就会被固定。如果您根据需要分配 SocketAsyncEventArgs 和字节缓冲区,由于碎片化和 GC 无法压缩固定内存,您可能会遇到许多客户端的 OutOfMemoryExceptions。

处理此问题的最佳方法是创建一个 SocketBufferPool 类,该类将在应用程序首次启动时分配大量字节和 SocketAsyncEventArgs,这样固定的内存将是连续的。然后根据需要简单地重新使用池中的缓冲区。

在实践中,我发现最好围绕 SocketAsyncEventArgs 创建一个包装类和一个 SocketBufferPool 类来管理资源的分配。

例如,这是 BeginReceive 方法的代码:

private void BeginReceive(Socket socket)
{
Contract.Requires(socket != null, "socket");

SocketEventArgs e = SocketBufferPool.Instance.Alloc();
e.Socket = socket;
e.Completed += new EventHandler<SocketEventArgs>(this.HandleIOCompleted);

if (!socket.ReceiveAsync(e.AsyncEventArgs)) {
this.HandleIOCompleted(null, e);
}
}

这是 HandleIOCompleted 方法:

private void HandleIOCompleted(object sender, SocketEventArgs e)
{
e.Completed -= this.HandleIOCompleted;
bool closed = false;

lock (this.sequenceLock) {
e.SequenceNumber = this.sequenceNumber++;
}

switch (e.LastOperation) {
case SocketAsyncOperation.Send:
case SocketAsyncOperation.SendPackets:
case SocketAsyncOperation.SendTo:
if (e.SocketError == SocketError.Success) {
this.OnDataSent(e);
}
break;
case SocketAsyncOperation.Receive:
case SocketAsyncOperation.ReceiveFrom:
case SocketAsyncOperation.ReceiveMessageFrom:
if ((e.BytesTransferred > 0) && (e.SocketError == SocketError.Success)) {
this.BeginReceive(e.Socket);
if (this.ReceiveTimeout > 0) {
this.SetReceiveTimeout(e.Socket);
}
} else {
closed = true;
}

if (e.SocketError == SocketError.Success) {
this.OnDataReceived(e);
}
break;
case SocketAsyncOperation.Disconnect:
closed = true;
break;
case SocketAsyncOperation.Accept:
case SocketAsyncOperation.Connect:
case SocketAsyncOperation.None:
break;
}

if (closed) {
this.HandleSocketClosed(e.Socket);
}

SocketBufferPool.Instance.Free(e);
}

以上代码包含在将引发 DataReceived 和 DataSent 事件的 TcpSocket 类中。需要注意的一件事是 SocketAsyncOperation.ReceiveMessageFrom: block;如果套接字没有错误,它会立即启动另一个 BeginReceive(),它将从池中分配另一个 SocketEventArgs。

另一个重要注意事项是在 HandleIOComplete 方法中设置的 SocketEventArgs SequenceNumber 属性。尽管异步请求将按照排队的顺序完成,但您仍然会受到其他线程竞争条件的影响。由于代码在引发 DataReceived 事件之前调用 BeginReceive,因此服务于原始 IOCP 的线程可能会在调用 BeginReceive 之后但在引发事件之前阻塞,而第二个异步接收在首先引发 DataReceived 事件的新线程上完成。虽然这是一种相当罕见的边缘情况,但它可能会发生,并且 SequenceNumber 属性使消费应用程序能够确保以正确的顺序处理数据。

另一个需要注意的领域是异步发送。通常,异步发送请求将同步完成(如果调用同步完成,则 SendAsync 将返回 false)并且会严重降低性能。在 IOCP 上返回的异步调用的额外开销实际上会导致比简单地使用同步调用更差的性能。异步调用需要两次内核调用和一次堆分配,而同步调用发生在堆栈上。

希望对您有所帮助, 账单

关于c# - 使用 SocketAsyncEventArgs 的服务器设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1538348/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com