我最近一直在爲項目使用SocketAsyncEventArgs,並且遇到了ReceiveAsync偶爾以與通過SendAsync發送的內容不同的順序獲取數據的問題。在SendAsync方法中發送的每個數據塊都保留,但塊不一定按正確的順序排列。也許我對SendAsync方法有不正確的理解,但我認爲特別是使用SocketType.Stream和ProtocolType.Tcp將確保順序得到維護。我知道底層進程將不可避免地破壞消息,並且ReceiveAsync通常會讀取少於緩衝區分配的內容。但我認爲發送和接收流將維持秩序。SocketAsyncEventArgs發送/接收訂單
我製作了一個顯示問題的測試控制檯程序。它每次使用不同的套接字和端口集嘗試運行約20次。在我的筆記本電腦上,它通常會通過一次,然後再次失敗;通常在期待第二個時候會收到一個後面的塊。從其他測試中,我知道預期的塊最終會出現,只是順序不對。
一個警告是我能夠在Windows 2008遠程服務器上測試它,並且沒有問題。但是,它從來沒有接近完成我的筆記本電腦。事實上,如果我讓調試執行在異常暫停中暫停一段時間,我已經不止一次地完全凍結了我的筆記本電腦,並且必須重新啓動硬盤。這是我使用VS2017在Windows 7上運行的筆記本電腦。我不確定它是否可能是一個因素,但它正在運行Symantec Endpoint Protection,儘管我沒有在日誌中找到任何內容。
所以我的問題是,我有一個SocketAsyncEventArgs如何操作的錯誤觀點?或者,我的代碼是一場災難(也許都是)?這對我的筆記本電腦來說有點獨特嗎? (這最後一個讓我覺得我設置了尷尬的時候你是新來的編程一樣,你覺得一定有什麼錯誤的編譯器。)
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
static class DumTest
{
static void Main(string[] args)
{
for (int i = 9177; i < 9199; i++)
{
RunDum(i);
//Thread.Sleep(350);
}
Console.WriteLine("all done.");
Console.ReadLine();
}
static void RunDum(int port)
{
var dr = new DumReceiver(port);
var ds = new DumSender(port);
dr.Acception.Wait();
ds.Connection.Wait();
dr.Completion.Wait();
ds.Completion.Wait();
Console.WriteLine($"Completed {port}. " +
$"sent: {ds.SegmentsSent} segments, received: {dr.SegmentsRead} segments");
}
}
class DumReceiver
{
private readonly SocketAsyncEventArgs eva = new SocketAsyncEventArgs();
private readonly TaskCompletionSource<object> tcsAcc = new TaskCompletionSource<object>();
private TaskCompletionSource<object> tcsRcv;
private Socket socket;
internal DumReceiver(int port)
{
this.eva.Completed += this.Received;
var lstSock = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var localIP = Dns.GetHostEntry(Dns.GetHostName()).AddressList
.First(i => i.AddressFamily == AddressFamily.InterNetwork);
lstSock.Bind(new IPEndPoint(localIP, port));
lstSock.Listen(1);
var saea = new SocketAsyncEventArgs();
saea.Completed += this.AcceptCompleted;
lstSock.AcceptAsync(saea);
}
internal Task Acception => this.tcsAcc.Task;
internal Task Completion { get; private set; }
internal int SegmentsRead { get; private set; }
private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
this.socket = e.AcceptSocket;
e.Dispose();
try
{
this.Completion = this.ReceiveLupeAsync();
}
finally
{
this.tcsAcc.SetResult(null);
}
}
else
{
this.tcsAcc.SetException(new SocketException((int)e.SocketError));
}
}
private async Task ReceiveLupeAsync()
{
var buf = new byte[8196];
byte bufSeg = 1;
int pos = 0;
while (true)
{
this.tcsRcv = new TaskCompletionSource<object>();
this.eva.SetBuffer(buf, pos, 8196 - pos);
if (this.socket.ReceiveAsync(this.eva))
{
await this.tcsRcv.Task.ConfigureAwait(false);
}
if (this.eva.SocketError != SocketError.Success)
{
throw new SocketException((int)eva.SocketError);
}
if (this.eva.BytesTransferred == 0)
{
if (pos != 0)
{
throw new EndOfStreamException();
}
break;
}
pos += this.eva.BytesTransferred;
if (pos == 8196)
{
pos = 0;
for (int i = 0; i < 8196; i++)
{
if (buf[i] != bufSeg)
{
var msg = $"Expected {bufSeg} but read {buf[i]} ({i} of 8196). " +
$"Last read: {this.eva.BytesTransferred}.";
Console.WriteLine(msg);
throw new Exception(msg);
}
}
this.SegmentsRead++;
bufSeg = (byte)(this.SegmentsRead + 1);
}
}
}
private void Received(object s, SocketAsyncEventArgs e) => this.tcsRcv.SetResult(null);
}
class DumSender
{
private readonly SocketAsyncEventArgs eva = new SocketAsyncEventArgs();
private readonly Socket socket = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private readonly TaskCompletionSource<object> tcsCon = new TaskCompletionSource<object>();
private TaskCompletionSource<object> tcsSnd;
internal DumSender(int port)
{
this.eva.Completed += this.Sent;
var saea = new SocketAsyncEventArgs();
var localIP = Dns.GetHostEntry(Dns.GetHostName()).AddressList
.First(i => i.AddressFamily == AddressFamily.InterNetwork);
saea.RemoteEndPoint = new IPEndPoint(localIP, port);
saea.Completed += this.ConnectionCompleted;
this.socket.ConnectAsync(saea);
}
internal Task Connection => this.tcsCon.Task;
internal Task Completion { get; private set; }
internal int SegmentsSent { get; private set; }
private void ConnectionCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
e.Dispose();
try
{
this.Completion = this.SendLupeAsync();
}
finally
{
this.tcsCon.SetResult(null);
}
}
else
{
this.tcsCon.SetException(new SocketException((int)e.SocketError));
}
}
private async Task SendLupeAsync()
{
var buf = new byte[8196];
byte bufSeg = 1;
while (true)
{
for (int i = 0; i < 8196; i++)
{
buf[i] = bufSeg;
}
this.tcsSnd = new TaskCompletionSource<object>();
this.eva.SetBuffer(buf, 0, 8196);
if (this.socket.SendAsync(this.eva))
{
await this.tcsSnd.Task.ConfigureAwait(false);
}
if (this.eva.SocketError != SocketError.Success)
{
throw new SocketException((int)this.eva.SocketError);
}
if (this.eva.BytesTransferred != 8196)
{
throw new SocketException();
}
if (++this.SegmentsSent == 299)
{
break;
}
bufSeg = (byte)(this.SegmentsSent + 1);
}
this.socket.Shutdown(SocketShutdown.Both);
}
private void Sent(object s, SocketAsyncEventArgs e) => this.tcsSnd.SetResult(null);
}
我無法使用您發佈的代碼重現您的問題(Windows 10 Pro)。代碼對我來說看起來沒問題。除非我需要最大的可伸縮性,否則我不會在Socket中使用'XXXAsync()'方法。使用'NetworkStream'和傳統的'await'友好的'XXXAsync()'方法,代碼更容易讀寫。但是我沒有在代碼中看到任何實際的_bug_,並且對於套接字中可用的任何異步機制,存在潛在的緩衝區重新排序問題,只有當您有兩個或多個併發排隊的讀取操作時纔會出現你沒有。 –
FWIW,我增加了測試代碼,以運行22個端口測試的20次迭代,其中2990個段而不是299個,並且仍然無法使其失敗。坦率地說,如果你有任何類型的AV軟件,那麼如果你開始看到來自I/O代碼的意外行爲,那應該總是你禁用的第一件事。這類軟件中的錯誤太常見了,而且在向更大的社區尋求幫助之前,不妨試試這種可能性,以便有任何藉口不會這樣做。 –
我很感激你花時間來測試它,並道歉,如果這只是浪費時間。我天真地認爲,因爲連接是從本地主機進行的,所以它不在AV的範圍內。我嘗試關閉它,然後用相同的結果測試它。這樣的公司設置並沒有讓它變得非常直觀或容易,所以它可能還在運行。我明天會有更多的選擇來測試它。感謝您的理智檢查。 – alex98101