净 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);
      }
    }