净 5 UDP Socket.ReceiveFromAsync
NET 5 UDP Socket.ReceiveFromAsync
我正在尝试将一些 NET Framework 4.8 异步 UDP 侦听器代码移植到 NET 5。代码位于 Windows Forms 应用程序中,该应用程序接收传入的 UDP 消息并将它们显示在一个简单的 ListBox 控件中。该代码在 NET Framework 上已经 运行 正常运行了一两年,没有出现任何故障。
问题是它不会 运行 在 NET 5 上可靠地运行。显示 window 显示 1 或 2 个传入的 UDP 消息,然后停止显示之后的任何内容。有时它甚至不会显示第一个传入的 UDP 消息。
我的理论(没有任何真实证据支持)是异步监听事件出了问题,应用程序在收到数据包后没有在回调中启动新的监听操作,因此停止监听端口.
我在网上搜索了与我的代码明显不同的 UDP 异步示例,搜索了热门示例站点,阅读了 Microsoft TCP 示例(周围有很多 TCP 示例),并在 SO 上搜索了这里,但都没有成功。
我尝试的最大更改是将简单的 ReceiveFromAsync 调用更改为监视可能 returned 的 willRaiseEvent 标志的调用,但这也无济于事。下面是我试过没有成功的修改调用代码的例子。
var willRaiseEvent = _sockReceiver.ReceiveFromAsync(newRxArgs);
if (!willRaiseEvent) ProcessMyData(newRxArgs);
我做的另一个改变是在回调方法中分配新的 SocketAsyncEventArgs 而不是重复使用旧的。这有时似乎有所帮助,并且在应用程序停止显示任何更多传入的 UDP 消息之前会显示多条日志消息。 (下面的代码是原始代码,因此没有显示我调试 NET 5 的修改。)
任何人都可以看到下面的代码可能 wrong/inadequate 已经运行了很长时间,或者我在 NET 5 上可能遗漏了什么?谢谢。
更新:
持续的调试工作表明回调方法中的 ReceiveFromAsync 调用总是 returns false(表明它不会引发接收完成事件)。如果它从不引发事件,那就可以解释为什么不显示日志消息。
另一个令人不安的事情是,即使我在调用 ReceiveFromAsync 之前为 SetBuffer 分配了新的 SocketEventArgs 和新缓冲区,ReceiveFromAsync 立即编辑的事件参数 return 包含刚刚处理的传入消息的尾部.据我所知,在回调中,ReceiveFromAsync 调用认为第二个接收 IO 事件实际上已经发生,但实际上并没有发生。 (我在调试器中,只向代码发送了 1 个数据包。)很奇怪。
我希望对 UDP ReceiveFromAsync 了解更多的人可以阐明正在发生的事情,尤其是关于 ReceiveFromAsync 应该和不应该 return 正确或错误的条件。
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);
}
我仍然不确定为什么原始代码不能在 NET 5 上运行。最后,我将回调方法重写为 1) 引发事件以通过某些非 UI 回调线程以外的线程和 2) 循环和处理数据包,直到 ReceiveFromAsync 最终返回 true。我的新代码如下所示。
重新使用事件参数对象或分配一个新对象根本没有明显的区别。出于性能原因,我重用了同一个对象,而不是为可重用对象池添加代码。
清除事件参数对象中的缓冲区(在原始代码中)也没有任何区别(所以我也将其删除)。
下面的代码似乎在 NET 5 上对我来说没有失败。我添加了一个循环来处理突发的高流量。循环一直旋转,直到 ReceiveFromAsync 最终 returns true 表示它将在收到下一个数据包时引发事件。
private void
UdpReceiveCallback(object sock, SocketAsyncEventArgs rxArgs) {
// the SockReceiver is the original socket that initiated the receive
// the rxArgs are the same ones passed in by the original caller
if (rxArgs.SocketError == SocketError.Success) {
// fire an event for asynch processing elsewhere
var textData = ExtractData(rxArgs);
var eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
// ReceiveFromAsync returns true if it will notify you in the future
var willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
// if it returns false it means it will not raise a future event because
// it has received a packet already. Since the listener can get a burst
// of packets from apps on the localhost, the loop below hands the
// processing off to another thread until ReceiveFromAsync finally
// returns true and says that it will raise an event in the future
// when the next packet arrives.
while (! willRaiseEvent) {
// fire an event for asynch processing elsewhere
textData = ExtractData(rxArgs);
eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
}
}
我正在尝试将一些 NET Framework 4.8 异步 UDP 侦听器代码移植到 NET 5。代码位于 Windows Forms 应用程序中,该应用程序接收传入的 UDP 消息并将它们显示在一个简单的 ListBox 控件中。该代码在 NET Framework 上已经 运行 正常运行了一两年,没有出现任何故障。
问题是它不会 运行 在 NET 5 上可靠地运行。显示 window 显示 1 或 2 个传入的 UDP 消息,然后停止显示之后的任何内容。有时它甚至不会显示第一个传入的 UDP 消息。
我的理论(没有任何真实证据支持)是异步监听事件出了问题,应用程序在收到数据包后没有在回调中启动新的监听操作,因此停止监听端口.
我在网上搜索了与我的代码明显不同的 UDP 异步示例,搜索了热门示例站点,阅读了 Microsoft TCP 示例(周围有很多 TCP 示例),并在 SO 上搜索了这里,但都没有成功。
我尝试的最大更改是将简单的 ReceiveFromAsync 调用更改为监视可能 returned 的 willRaiseEvent 标志的调用,但这也无济于事。下面是我试过没有成功的修改调用代码的例子。
var willRaiseEvent = _sockReceiver.ReceiveFromAsync(newRxArgs);
if (!willRaiseEvent) ProcessMyData(newRxArgs);
我做的另一个改变是在回调方法中分配新的 SocketAsyncEventArgs 而不是重复使用旧的。这有时似乎有所帮助,并且在应用程序停止显示任何更多传入的 UDP 消息之前会显示多条日志消息。 (下面的代码是原始代码,因此没有显示我调试 NET 5 的修改。)
任何人都可以看到下面的代码可能 wrong/inadequate 已经运行了很长时间,或者我在 NET 5 上可能遗漏了什么?谢谢。
更新:
持续的调试工作表明回调方法中的 ReceiveFromAsync 调用总是 returns false(表明它不会引发接收完成事件)。如果它从不引发事件,那就可以解释为什么不显示日志消息。
另一个令人不安的事情是,即使我在调用 ReceiveFromAsync 之前为 SetBuffer 分配了新的 SocketEventArgs 和新缓冲区,ReceiveFromAsync 立即编辑的事件参数 return 包含刚刚处理的传入消息的尾部.据我所知,在回调中,ReceiveFromAsync 调用认为第二个接收 IO 事件实际上已经发生,但实际上并没有发生。 (我在调试器中,只向代码发送了 1 个数据包。)很奇怪。
我希望对 UDP ReceiveFromAsync 了解更多的人可以阐明正在发生的事情,尤其是关于 ReceiveFromAsync 应该和不应该 return 正确或错误的条件。
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);
}
我仍然不确定为什么原始代码不能在 NET 5 上运行。最后,我将回调方法重写为 1) 引发事件以通过某些非 UI 回调线程以外的线程和 2) 循环和处理数据包,直到 ReceiveFromAsync 最终返回 true。我的新代码如下所示。
重新使用事件参数对象或分配一个新对象根本没有明显的区别。出于性能原因,我重用了同一个对象,而不是为可重用对象池添加代码。
清除事件参数对象中的缓冲区(在原始代码中)也没有任何区别(所以我也将其删除)。
下面的代码似乎在 NET 5 上对我来说没有失败。我添加了一个循环来处理突发的高流量。循环一直旋转,直到 ReceiveFromAsync 最终 returns true 表示它将在收到下一个数据包时引发事件。
private void
UdpReceiveCallback(object sock, SocketAsyncEventArgs rxArgs) {
// the SockReceiver is the original socket that initiated the receive
// the rxArgs are the same ones passed in by the original caller
if (rxArgs.SocketError == SocketError.Success) {
// fire an event for asynch processing elsewhere
var textData = ExtractData(rxArgs);
var eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
// ReceiveFromAsync returns true if it will notify you in the future
var willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
// if it returns false it means it will not raise a future event because
// it has received a packet already. Since the listener can get a burst
// of packets from apps on the localhost, the loop below hands the
// processing off to another thread until ReceiveFromAsync finally
// returns true and says that it will raise an event in the future
// when the next packet arrives.
while (! willRaiseEvent) {
// fire an event for asynch processing elsewhere
textData = ExtractData(rxArgs);
eventData = new TextReceivedEventData(textData);
RaiseTextReceived(eventData);
willRaiseEvent = ((Socket) sock).ReceiveFromAsync(rxArgs);
}
}