簡體   English   中英

C# SocketAsyncEventArgs 未發送所有數據

[英]C# SocketAsyncEventArgs not sending all data

我的 SocketAsyncEventArgs class 有問題。問題是當我嘗試通過 Internet 發送 8K 數據時,套接字有時只發送 1K 或 2K,我知道這對於 TCP 套接字是正常的,而一個發送沒有保證一收。 現在為了這對我有用,我修改了我的代碼以重新發送剩余的數據,例如當我 SocketAsyncEventArgs.SendAsync 完成時,在回調中我檢查它是否發送了所有 8K,如果不是,我調用 SocketAsyncEventArgs.SendAsync再次使用剩余的數據,直到我全部發送。 現在,當我查看一些 SocketAsyncEventArgs 代碼時。我看到大多數人不這樣做,他們只是在發送完成時清理而不檢查它是否發送了所有數據。 當我看微軟的例子時也是如此。 他們說對 SocketAsyncEventArgs.SendAsync 的 ONE 調用保證所有數據都將被發送。 我的意思是我自己測試了它,並且不會在一次調用 SocketAsyncEventArgs?SendAsync 時發送所有數據。 我做錯了什么? 提前致謝。

編輯:這是不發送所有數據的代碼(就像微軟的一樣),當套接字發送例如 1Kb 的數據而不是全部 8K 時,將調用 SendAsyncComplete!

public virtual void Send(byte[] packet, int offset, int length)
{
    if (_tcpSock != null && _tcpSock.Connected)
    {
        var args = SocketHelpers.AcquireSocketArg();
        if (args != null)
        {
            args.Completed += SendAsyncComplete;
            args.SetBuffer(packet, offset, length);
            args.UserToken = this;

            var willRaiseEvent = _tcpSock.SendAsync(args);
            if (!willRaiseEvent)
            {
            ProcessSend(args);
            }
            unchecked
            {
                _bytesSent += (uint)length;
            }
            Interlocked.Add(ref _totalBytesSent, length);
        }
        else
        {
            log.Error("Client {0}'s SocketArgs are null", this);
        }
    }
}

private static void ProcessSend(SocketAsyncEventArgs args)
{
      args.Completed -= SendAsyncComplete;
      SocketHelpers.ReleaseSocketArg(args);   
}

private static void SendAsyncComplete(object sender, SocketAsyncEventArgs args)
{
    ProcessSend(args);
}

有很多東西我會在那里改變。 作為序言,請閱讀

首先,每當您在套接字上發送任何數據時,您都必須處理該事件:要么停止整個發送過程,要么發出另一個套接字發送操作來發送剩余的數據。

所以有 3 種方法是有意義的,如下所示:

// This is the Send() to be used by your class' clients
1) public void Send(byte[] buffer);

此方法將注意應用您需要的任何數據格式,創建(檢索)一個 SocketAsyncEventArgs object,設置令牌以保存您的緩沖區,然后調用下面的下一個方法:

2) private void Send(SocketAsyncEventArgs e);

這實際上調用了 Socket.SendAsync(SocketAsyncEventArgs e) 並將內容從令牌(緩沖區,還記得嗎?)復制到 SAEA object。 現在這就是為什么因為方法號 (2) 可能會被調用多次以發送無法通過套接字在一次操作中發送的剩余數據。 所以在這里你將剩余的數據從令牌復制到 SAEA 緩沖區。

3) private void ProcessSent(SocketAsyncEventArgs e);

最后一種方法將檢查套接字已發送的數據。 如果所有數據都發送完畢,SAEA object 將被釋放。 如果不是,則對數據的 rest 再次調用方法(2)。 為了跟蹤發送的數據,您使用 SAEA.BytesTransferred。 您應該將此值添加到存儲在我建議您創建的自定義令牌中的值中(因此不要使用“this”作為令牌)。

您還可以在此處檢查 SAEA 參數上的 SocketError。

最后一個方法將在兩個地方調用:

  • 在第二種方法中,像這樣:

     // Attempt to send data in an asynchronous fashion bool isAsync = this.Socket.SendAsync(e); // Something went wrong and we didn't send the data async if (.isAsync) this;ProcessSent(e);

這一點很重要,即使在使用更傳統的 Begin/EndXXX 模式(在這種情況下,通過 IAsyncResult)時,很多人都錯過了它。 如果你不放置它,偶爾(非常罕見)會突然彈出一個 StackOverflow 異常,讓你困惑很長時間。

  • 在 Completed 事件處理程序中:

     private void Completed(object sender, SocketAsyncEventArgs e) { // What type of operation did just completed? switch (e.LastOperation) { case SocketAsyncOperation.Send: { ProcessSent(e); break; } } }

棘手的是每次第一次 Send(byte[]) 操作使用一個 SocketAsyncEventArgs object,如果所有數據都已發送,則在第三次操作中釋放它。

為此,您必須創建一個自定義令牌(類或不可變結構)以放置在第一個方法內的 SocketAsyncEventArgs.UserToken 中,然后跟蹤您在每個 Socket.SendAsync() 操作中傳輸了多少數據。

當您閱讀開頭提供的文章時,請注意作者如何重用相同的 SAEA object,如果所有數據都已發送,則在 Send() 操作結束時繼續執行 Receive() 操作。 那是因為他的協議是:每一方(服務器和客戶端)輪流相互交談。

現在,如果同時發生對第一個 Send() 方法的多個調用,則操作系統將按什么順序處理它們並沒有規則。 如果這很可能發生並且消息順序很重要,並且由於從“外部實體”對 Send(byte[]) 的任何調用都會導致 Socket.SendAsync(),我建議第一種方法實際上寫下接收到的字節在內部緩沖區中。 只要此緩沖區不為空,您就可以在內部繼續發送此數據。 把它想象成一個生產者-消費者場景,其中“外部實體”是生產者,內部發送操作是消費者。 我更喜歡這里的樂觀並發場景。

關於這件事的文檔相當淺薄,除了這篇文章,即使在這種情況下,當您開始實現自己的應用程序時,有些事情會變得不同。 這個 SocketAsyncEventArgs model 可以說有點違反直覺。

如果您需要更多幫助,請告訴我,不久前,當我開發自己的庫時,我曾為此苦苦掙扎。

編輯:

如果我是你,我會搬家

unchecked
{
    _bytesSent += (uint)length;
}
Interlocked.Add(ref _totalBytesSent, length);

到您的 ProcessSend(SAEA),並使用 args.BytesTransferred 而不是“長度”。

暫無
暫無

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

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