【发布时间】:2021-12-31 06:36:52
【问题描述】:
我正在尝试将一些 NET Framework 4.8 异步 UDP 侦听器代码移植到 NET 5。该代码位于一个 Windows 窗体应用程序中,该应用程序接收传入的 UDP 消息并将它们显示在一个简单的 ListBox 控件中。代码在 NET Framework 上运行良好一两年没有出现故障。
问题在于它不能在 NET 5 上可靠地运行。显示窗口显示 1 或 2 条传入的 UDP 消息,然后停止显示任何内容。有时它甚至不会显示第一条传入的 UDP 消息。
我的理论(没有任何真实证据支持)是异步侦听器事件出现问题,应用程序在收到数据包后不会在回调中启动新的侦听操作,因此停止侦听端口。
我已经在网上扫描了与我的代码明显不同的 UDP 异步示例,搜索了热门示例站点,阅读了 Microsoft TCP 示例(周围有很多 TCP 示例),并在此处搜索了 SO,但所有没有成功。
我尝试的最大更改是将简单的 ReceiveFromAsync 调用更改为监视可能返回的 willRaiseEvent 标志的调用,但这也无济于事。这是我尝试过的修改后的调用代码示例,但没有成功。
var willRaiseEvent = _sockReceiver.ReceiveFromAsync(newRxArgs);
if (!willRaiseEvent) ProcessMyData(newRxArgs);
我所做的另一个更改是在回调方法中分配新的 SocketAsyncEventArgs,而不是重用旧的。这有时似乎有所帮助,并且在应用程序停止显示更多传入的 UDP 消息之前会显示多条日志消息。 (下面的代码是原始代码,因此没有显示我调试 NET 5 的修改。)
谁能看到下面已经运行了很长时间的代码可能有什么问题/不足,或者我在 NET 5 上可能缺少什么?谢谢。
更新:
继续调试工作表明,回调方法中的 ReceiveFromAsync 调用始终返回 false(表示它不会引发接收完成事件)。如果它从不引发事件,那就可以解释为什么不显示日志消息。
另一个令人不安的事情是 ReceiveFromAsync 立即返回的事件参数包含刚刚处理的传入消息的尾随部分,即使我在调用 ReceiveFromAsync 之前为 SetBuffer 分配了新的 SocketEventArgs 和新的缓冲区。据我所知,在回调中,ReceiveFromAsync 调用认为实际上已经发生了第二个接收 IO 事件,而实际上并没有发生。 (我在调试器中,只向代码发送了 1 个数据包。)很奇怪。
我希望对 UDP ReceiveFromAsync 有更多了解的人能够对正在发生的事情有所了解,尤其是关于 ReceiveFromAsync 应该和不应该返回 true 或 false 的条件。
public void UdpReceivePacket48() {
try {
// listen on the port from any IP address
_endPointReceiveFrom = new IPEndPoint(IPAddress.Any, Port);
_sockReceiver.Bind(_endPointReceiveFrom);
// make a localhost endpoint for the receive operation
var myReturnAddress = IPAddress.Parse(Localhost);
_endPointSendTo = new IPEndPoint(myReturnAddress, Port);
// fill out the event arguments and callback method
var rxEventArgs = new SocketAsyncEventArgs();
rxEventArgs.RemoteEndPoint = _endPointSendTo;
rxEventArgs.SetBuffer(new byte[_cbuffersize], 0, _cbuffersize);
rxEventArgs.Completed += UdpReceiveCallback48;
// initiate a receive operation
_sockReceiver.ReceiveFromAsync(rxEventArgs);
}
catch (Exception e) {
LogTheFailure();
throw;
}
}
void UdpReceiveCallback48(object sock, SocketAsyncEventArgs rxArgs) {
var bytesRx = rxArgs.BytesTransferred;
var textReceived = Encoding.ASCII.GetString(rxArgs.Buffer, 0, bytesRx);
Console.WriteLine($@"Text received: {textReceived}");
// clear the buffer before you start another receive operation
Array.Clear(rxArgs.Buffer, 0, rxArgs.Count);
// The event args are the original ones, so you can reuse them all.
((Socket) sock).ReceiveFromAsync(rxArgs);
// process the received data (not on the UI thread)
Program.ListBoxLog.AddToLog(textReceived);
}
【问题讨论】:
-
您的旧代码使用单个
SocketAsyncEventArgs来处理所有传入的 UDP 数据包。当负载不高并且 .NET Framework 由于其限制而减慢速度时,这可能会起作用。 .NET Core 在与套接字相关的场景中有显着改进,所以我认为您的代码在那里可能无法很好地工作。您要么从现有项目中学习高性能套接字模式,例如 SuperSocket,要么学习新的基于管道的套接字 API。没有一种方法适合简短的答案。 -
感谢您的观点。碰巧,你是对的,我的代码绝对不能在 NET 5 中运行!我保证没有高负载(它也发生在调试器中)。我认为问题在于回调方法中的 ReceiveFromAsync 调用“总是”返回 false,表明它将来不会引发事件(这可能解释了为什么不再显示消息)。但我不知道为什么它应该返回 false,也不知道我能做些什么。
-
我也尝试过不在回调方法中重用事件参数。但是 ReceiveFromAsync 调用仍然返回 false(通常在返回的事件参数中包含部分原始数据包数据)。