我最近一直在为一个项目使用SocketAsyncEventArgs,而且我遇到的问题是,ReceiveAsync偶尔会以与SendAsync发送的顺序不同的顺序获取数据。在SendAsync方法中发送的每个数据块都会被维护,但这些块不一定是正确的顺序。也许我对SendAsync方法的理解不正确,但我认为特别是使用SocketType.Stream和ProtocolType.Tcp会确保维护订单。我知道底层进程将不可避免地破坏消息,并且ReceiveAsync通常读取的内存少于缓冲区分配。但我假设发送和接收流将维持秩序。
我制作了一个显示问题的测试控制台程序。它每次尝试使用一组不同的套接字和端口运行大约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);
}
答案 0 :(得分:0)
我认为问题出在您的代码中。
您必须检查使用Socket
的{{1}} *Async
方法的返回情况。如果他们返回SocketAsyncEventArgs
,则false
事件不会被提升,您必须同步处理结果。
参考文档:SocketAsyncEventArgs Class。搜索SocketAsyncEventArgs.Completed
。
在willRaiseEvent
的构造函数中,您无法检查DumReceiver
的结果,并且在同步完成时您无法处理该案例。< / p>
在AcceptAsync
的构造函数中,您无法检查DumSender
的结果,并且在同步完成时您无法处理该案例。< / p>
除此之外,ConnectAsync
事件可能会在某个其他线程中引发,很可能是来自SocketAsyncEventArgs.Completed
的I / O线程。
每次在没有正确同步的情况下分配给ThreadPool
和DumReceiver.tcsRcv
时,您无法确定DumSender.tcsSnd
和DumReceiver.Received
是否正在使用最新的{{1} }}
实际上,你可以在第一次迭代时获得DumSender.Sent
。
你缺乏同步:
TaskCompletionSource
,字段NullReferenceException
和DumReceiver
以及属性tcsRcv
和socket
Completion
,字段SegmentsRead
以及属性DumSender
和tcsSnd
我建议您在每次调用Completion
和SegmentsSent
时考虑使用单个SemaphoreSlim
而不是创建新的TaskCompletionSource
。您在构造函数中将信号量初始化为0。如果ReceiveAsync
操作处于待处理状态,则您在信号量上SendAsync
,而*Async
事件将await WaitAsync
信号量。
这应该足以摆脱Completed
字段中的竞争条件。您仍然需要在其他字段和属性上进行适当的同步。例如,没有理由不能在构造函数中创建Release
,TaskCompletionSource
和Completion
可以是只读的并引用一个字段将使用一个或多个SegmentsRead
方法在内部访问(例如SegmentsSent
或Interlocked
)。