简体   繁体   English

使用SocketAsyncEventArgs的服务器设计

[英]Server design using SocketAsyncEventArgs

I want to create an asynchronous socket Server using the SocketAsyncEventArgs event. 我想使用SocketAsyncEventArgs事件创建一个异步套接字服务器。

The server should manage about 1000 connections at the same time. 服务器应该同时管理大约1000个连接。 What is the best way to handle the logic for each packet? 处理每个数据包逻辑的最佳方法是什么?

The Server design is based on this MSDN example , so every socket will have his own SocketAsyncEventArgs for receiving data. 服务器设计基于此MSDN示例 ,因此每个套接字都有自己的SocketAsyncEventArgs用于接收数据。

  1. Do the logic stuff inside the receive function . 在接收函数中做逻辑内容 No overhead will be created, but since the next ReceiveAsync() call won't be done before the logic has completed, new data can't be read from the socket. 不会产生任何开销,但由于在逻辑完成之前不会完成下一个ReceiveAsync()调用,因此无法从套接字读取新数据。 The two main questions for me are: If the client sends a lot of data and the logic processing is heavy, how will the system handle it (packets lost because buffer is to full)? 对我来说两个主要问题是:如果客户端发送大量数据并且逻辑处理很重,系统将如何处理它(由于缓冲区已满而丢失数据包)? Also, if all clients send data at the same time, will there be 1000 threads, or is there an internal limit and a new thread can't start before another one completes execution? 此外,如果所有客户端同时发送数据,是否会有1000个线程,或者是否存在内部限制,并且在另一个线程完成执行之前新线程无法启动?

  2. Use a queue. 使用队列。 The receive function will be very short and execute fast, but you'll have decent overhead because of the queue. 接收函数将非常短并且执行速度很快,但由于队列的原因,您将获得不错的开销。 Problems are, if your worker threads are not fast enough under heavy server load, your queue can get full, so maybe you have to force packet drops. 问题是,如果你的工作线程在服务器负载很重的情况下速度不够快,你的队列就会变满,所以也许你必须强制丢包。 You also get the Producer/Consumer problem, which can probably slow down the entire queue with to many locks. 您还会遇到生产者/消费者问题,这可能会导致整个队列的速度变慢。

So what will be the better design, logic in receive function, logic in worker threads or anything completely different I've missed so far. 那么更好的设计,接收函数中的逻辑,工作线程中的逻辑或者到目前为止我所遗漏的任何完全不同的东西。

Another Quest regarding data sending. 关于数据发送的另一个任务。

Is it better to have a SocketAsyncEventArgs tied to a socket (analog to the receive event) and use a buffer system to make one send call for a few small packets (let's say the packets would otherwise sometimes! send directly one after another) or use a different SocketAsyncEventArgs for every packet and store them in a pool to reuse them? 将SocketAsyncEventArgs绑定到套接字(类似于接收事件)并使用缓冲系统对一些小数据包进行一次发送调用(假设有时会直接一个接一个地发送数据包)或者使用它是否更好?每个数据包使用不同的SocketAsyncEventArgs并将它们存储在池中以重用它们?

To effectivly implement async sockets each socket will need more than 1 SocketAsyncEventArgs. 要有效地实现异步套接字,每个套接字将需要多于1个SocketAsyncEventArgs。 There is also an issue with the byte[] buffer in each SocketAsyncEventArgs. 每个SocketAsyncEventArgs中的byte []缓冲区也存在问题。 In short, the byte buffers will be pinned whenever a managed - native transition occurs (sending / receiving). 简而言之,只要发生托管 - 本机转换(发送/接收),就会固定字节缓冲区。 If you allocate the SocketAsyncEventArgs and byte buffers as needed you can run into OutOfMemoryExceptions with many clients due to fragmentation and the inability of the GC to compact pinned memory. 如果根据需要分配SocketAsyncEventArgs和字节缓冲区,则由于碎片和GC无法压缩固定内存,因此可能会遇到包含许多客户端的OutOfMemoryExceptions。

The best way to handle this is to create a SocketBufferPool class that will allocate a large number of bytes and SocketAsyncEventArgs when the application is first started, this way the pinned memory will be contiguous. 处理此问题的最佳方法是创建一个SocketBufferPool类,它将在应用程序首次启动时分配大量字节和SocketAsyncEventArgs,这样固定内存将是连续的。 Then simply resuse the buffers from the pool as needed. 然后根据需要简单地从池中重新使用缓冲区。

In practice I've found it best to create a wrapper class around the SocketAsyncEventArgs and a SocketBufferPool class to manage the distribution of resources. 在实践中,我发现最好围绕SocketAsyncEventArgs和SocketBufferPool类创建一个包装类来管理资源的分配。

As an example, here is the code for a BeginReceive method: 作为示例,这是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);
        }
    }

And here is the HandleIOCompleted method: 这是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);
    }

The above code is contained in a TcpSocket class that will raise DataReceived & DataSent events. 上面的代码包含在TcpSocket类中,该类将引发DataReceived和DataSent事件。 One thing to notice is the case SocketAsyncOperation.ReceiveMessageFrom: block; 需要注意的一件事是SocketAsyncOperation.ReceiveMessageFrom:block; if the socket hasn't had an error it immediately starts another BeginReceive() which will allocate another SocketEventArgs from the pool. 如果套接字没有出错,它立即启动另一个BeginReceive(),它将从池中分配另一个SocketEventArgs。

Another important note is the SocketEventArgs SequenceNumber property set in the HandleIOComplete method. 另一个重要的注意事项是HandleIOComplete方法中设置的SocketEventArgs SequenceNumber属性。 Although async requests will complete in the order queued, you are still subject to other thread race conditions. 虽然异步请求将在排队的顺序中完成,但您仍然受其他线程竞争条件的限制。 Since the code calls BeginReceive before raising the DataReceived event there is a possibility that the thread servicing the orginal IOCP will block after calling BeginReceive but before rasing the event while the second async receive completes on a new thread which raises the DataReceived event first. 由于代码在引发DataReceived事件之前调用BeginReceive,因此服务原始IOCP的线程有可能在调用BeginReceive之后但在第二次异步接收在第一次引发DataReceived事件的新线程上完成第二次异步接收之前阻塞事件。 Although this is a fairly rare edge case it can occur and the SequenceNumber property gives the consuming app the ability to ensure that data is processed in the correct order. 虽然这是一个相当罕见的边缘情况,但它可以发生,并且SequenceNumber属性使消费应用程序能够确保以正确的顺序处理数据。

One other area to be aware of is async sends. 另一个需要注意的领域是异步发送。 Oftentimes, async send requests will complete synchronously (SendAsync will return false if the call completed synchronously) and can severely degrade performance. 通常,异步发送请求将同步完成(如果调用同步完成,SendAsync将返回false)并且可能严重降低性能。 The additional overhead of of the async call coming back on an IOCP can in practice cause worse performance than simply using the synchronous call. 在IOCP上返回的异步调用的额外开销实际上可能导致比仅使用同步调用更差的性能。 The async call requires two kernel calls and a heap allocation while the synchronous call happens on the stack. 当同步调用发生在堆栈上时,异步调用需要两个内核调用和一个堆分配。

Hope this helps, Bill 比尔希望这有帮助

In your code, you do this: 在您的代码中,您执行此操作:

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

But it is an error to do that. 但这样做是错误的。 There is a reason why the callback is not invoked when it finishes synchronously, such action can fill up the stack. 有一个原因是为什么在同步完成时不会调用回调,这样的操作可以填满堆栈。

Imagine that each ReceiveAsync is always returning synchronously. 想象一下,每个ReceiveAsync总是同步返回。 If your HandleIOCompleted was in a while, you could process the result that returned synchronously at the same stack level. 如果你的HandleIOCompleted有一段时间,你可以处理在同一堆栈级别同步返回的结果。 If it didn't return synchronously, you break the while. 如果没有同步返回,你就会打破。 But, by doing the you you do, you end-up creating a new item in the stack... so if you have bad luck enough, you will cause stack overflow exceptions. 但是,通过执行您的操作,您最终会在堆栈中创建一个新项目......所以如果运气不好,您将导致堆栈溢出异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用SocketAsyncEventArgs编写可扩展的套接字服务器 - Write high-scalable socket server using SocketAsyncEventArgs 双工服务器环境中的SocketAsyncEventArgs? - SocketAsyncEventArgs in a duplex server environment? C#使用span与SocketAsyncEventArgs - C# Using span with SocketAsyncEventArgs 使用 SocketAsyncEventArgs 时的 TaskCompletionSource 死锁 - TaskCompletionSource deadlock while using SocketAsyncEventArgs SocketAsyncEventArgs UDP服务器缓冲区偶尔会充满零 - SocketAsyncEventArgs UDP Server Buffer is occassionally full of zeroes Windows Server 2003上的C#SocketAsyncEventArgs问题 - C# SocketAsyncEventArgs Issue on Windows Server 2003 现实生活(无回显服务器)需要 SocketAsyncEventArgs 教程 - Real life (NO echo server) SocketAsyncEventArgs tutorial needed TCP服务器重置SocketAsyncEventArgs实例上的AcceptSocket.ReceiveAsync操作 - TCP Server Resetting AcceptSocket.ReceiveAsync operation on a SocketAsyncEventArgs instance UDP SocketAsyncEventArgs 服务器和客户端发送和接收的数据不匹配 - UDP SocketAsyncEventArgs mismatching data sent and received by server and client 使用SocketAsyncEventArgs时是否可以删除ExecutionContext和Thread分配? - Is it possible to remove ExecutionContext and Thread allocations when using SocketAsyncEventArgs?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM