簡體   English   中英

使用SocketAsyncEventArgs的服務器設計

[英]Server design using SocketAsyncEventArgs

我想使用SocketAsyncEventArgs事件創建一個異步套接字服務器。

服務器應該同時管理大約1000個連接。 處理每個數據包邏輯的最佳方法是什么?

服務器設計基於此MSDN示例 ,因此每個套接字都有自己的SocketAsyncEventArgs用於接收數據。

  1. 在接收函數中做邏輯內容 不會產生任何開銷,但由於在邏輯完成之前不會完成下一個ReceiveAsync()調用,因此無法從套接字讀取新數據。 對我來說兩個主要問題是:如果客戶端發送大量數據並且邏輯處理很重,系統將如何處理它(由於緩沖區已滿而丟失數據包)? 此外,如果所有客戶端同時發送數據,是否會有1000個線程,或者是否存在內部限制,並且在另一個線程完成執行之前新線程無法啟動?

  2. 使用隊列。 接收函數將非常短並且執行速度很快,但由於隊列的原因,您將獲得不錯的開銷。 問題是,如果你的工作線程在服務器負載很重的情況下速度不夠快,你的隊列就會變滿,所以也許你必須強制丟包。 您還會遇到生產者/消費者問題,這可能會導致整個隊列的速度變慢。

那么更好的設計,接收函數中的邏輯,工作線程中的邏輯或者到目前為止我所遺漏的任何完全不同的東西。

關於數據發送的另一個任務。

將SocketAsyncEventArgs綁定到套接字(類似於接收事件)並使用緩沖系統對一些小數據包進行一次發送調用(假設有時會直接一個接一個地發送數據包)或者使用它是否更好?每個數據包使用不同的SocketAsyncEventArgs並將它們存儲在池中以重用它們?

要有效地實現異步套接字,每個套接字將需要多於1個SocketAsyncEventArgs。 每個SocketAsyncEventArgs中的byte []緩沖區也存在問題。 簡而言之,只要發生托管 - 本機轉換(發送/接收),就會固定字節緩沖區。 如果根據需要分配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);
    }

上面的代碼包含在TcpSocket類中,該類將引發DataReceived和DataSent事件。 需要注意的一件事是SocketAsyncOperation.ReceiveMessageFrom:block; 如果套接字沒有出錯,它立即啟動另一個BeginReceive(),它將從池中分配另一個SocketEventArgs。

另一個重要的注意事項是HandleIOComplete方法中設置的SocketEventArgs SequenceNumber屬性。 雖然異步請求將在排隊的順序中完成,但您仍然受其他線程競爭條件的限制。 由於代碼在引發DataReceived事件之前調用BeginReceive,因此服務原始IOCP的線程有可能在調用BeginReceive之后但在第二次異步接收在第一次引發DataReceived事件的新線程上完成第二次異步接收之前阻塞事件。 雖然這是一個相當罕見的邊緣情況,但它可以發生,並且SequenceNumber屬性使消費應用程序能夠確保以正確的順序處理數據。

另一個需要注意的領域是異步發送。 通常,異步發送請求將同步完成(如果調用同步完成,SendAsync將返回false)並且可能嚴重降低性能。 在IOCP上返回的異步調用的額外開銷實際上可能導致比僅使用同步調用更差的性能。 當同步調用發生在堆棧上時,異步調用需要兩個內核調用和一個堆分配。

比爾希望這有幫助

在您的代碼中,您執行此操作:

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

但這樣做是錯誤的。 有一個原因是為什么在同步完成時不會調用回調,這樣的操作可以填滿堆棧。

想象一下,每個ReceiveAsync總是同步返回。 如果你的HandleIOCompleted有一段時間,你可以處理在同一堆棧級別同步返回的結果。 如果沒有同步返回,你就會打破。 但是,通過執行您的操作,您最終會在堆棧中創建一個新項目......所以如果運氣不好,您將導致堆棧溢出異常。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM