【问题标题】:NET 5 UDP Socket.ReceiveFromAsyncNET 5 UDP Socket.ReceiveFromAsync
【发布时间】: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(通常在返回的事件参数中包含部分原始数据包数据)。

标签: c# sockets udp listener


【解决方案1】:

我仍然不确定为什么原始代码不能在 NET 5 上运行。最后,我将回调方法重写为 1) 引发事件以通过回调以外的某些非 UI 线程处理传入数据线程和 2) 循环和处理数据包,直到 ReceiveFromAsync 最终返回 true。我的新代码如下所示。

重用事件 args 对象或分配一个新对象根本没有明显的区别。出于性能原因,我重用了同一个对象,而不是为可重用对象池添加代码。

清除事件 args 对象中的缓冲区(在原始代码中)也没有任何区别(所以我也将其删除了)。

对于我来说,下面的代码在 NET 5 上似乎可以正常工作。我添加了一个循环来处理突发的高流量。循环一直旋转,直到 ReceiveFromAsync 最终返回 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);
      }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-08
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多