如何在循环中使用 UdpClient.BeginReceive

Posted

技术标签:

【中文标题】如何在循环中使用 UdpClient.BeginReceive【英文标题】:How to use UdpClient.BeginReceive in a loop 【发布时间】:2011-05-06 07:35:25 【问题描述】:

我想这样做

for (int i = 0; i < 100; i++ )

    Byte[] receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);

但我必须使用UdpClient.BeginReceive,而不是使用UdpClient.Receive。问题是,我该怎么做?使用BeginReceive 的示例并不多,MSDN 示例根本没有帮助。我应该使用BeginReceive,还是在单独的线程下创建它?

我一直得到ObjectDisposedException 异常。我只收到发送的第一个数据。下一个数据会抛出异常。

public class UdpReceiver

    private UdpClient _client;
    public System.Net.Sockets.UdpClient Client
    
        get  return _client; 
        set  _client = value; 
    
    private IPEndPoint _endPoint;
    public System.Net.IPEndPoint EndPoint
    
        get  return _endPoint; 
        set  _endPoint = value; 
    
    private int _packetCount;
    public int PacketCount
    
        get  return _packetCount; 
        set  _packetCount = value; 
    
    private string _buffers;
    public string Buffers
    
        get  return _buffers; 
        set  _buffers = value; 
    
    private Int32 _counter;
    public System.Int32 Counter
    
        get  return _counter; 
        set  _counter = value; 
    
    private Int32 _maxTransmission;
    public System.Int32 MaxTransmission
    
        get  return _maxTransmission; 
        set  _maxTransmission = value; 
    

    public UdpReceiver(UdpClient udpClient, IPEndPoint ipEndPoint, string buffers, Int32 counter, Int32 maxTransmission)
    
        _client = udpClient;
        _endPoint = ipEndPoint;
        _buffers = buffers;
        _counter = counter;
        _maxTransmission = maxTransmission;
    
    public void StartReceive()
    
        _packetCount = 0;
        _client.BeginReceive(new AsyncCallback(Callback), null);
    

    private void Callback(IAsyncResult result)
    
        try
        
            byte[] buffer = _client.EndReceive(result, ref _endPoint);
            // Process buffer
            MainWindow.Log(Encoding.ASCII.GetString(buffer));
            _packetCount += 1;
            if (_packetCount < _maxTransmission)
            
                _client.BeginReceive(new AsyncCallback(Callback), null);
            
        
        catch (ObjectDisposedException ex) 
        
            MainWindow.Log(ex.ToString());
        
        catch (SocketException ex) 
         
            MainWindow.Log(ex.ToString()); 
        
        catch (System.Exception ex)
        
            MainWindow.Log(ex.ToString()); 
        
    

什么给了?

顺便说一下,大致思路是:

    创建 tcpclient 管理器。 开始使用 udpclient 发送/接收数据。 当所有数据都发送完毕后,tcpclient 管理器将向接收方发出所有数据已发送的信号,并且应关闭 udpclient 连接。

【问题讨论】:

您当然知道 UDP 是一种可以丢失数据包的协议,并且不保证唯一性和顺序,因此尝试从特定端点接收 100 个数据包并不一定意味着您收到了相同的 100 个数据包按顺序发送的数据包?也许你应该使用 TCP? 我非常清楚这一点。这样做的原因是因为,我想分析两方之间的连接,即带宽估计。 socket beginreceive (async) in loop 很难实现,可以尝试使用 NetworkStream.DataAvailable 组合进行同步循环。提示:您可以将 BeginSend (async) 与相同的套接字一起使用。 【参考方案1】:

UdpClient.BeginReceive()UdpClient.EndReceive() 似乎没有很好地实现/理解。当然,与 TcpListener 的实现方式相比,使用起来要困难得多。

您可以做几件事来使UdpClient.Receive() 更适合您。首先,在底层套接字客户端上设置超时将使控制能够通过(到异常),允许控制流继续或按照你的意愿循环。其次,通过在一个新线程上创建 UDP 监听器(我没有展示它的创建),你可以避免 UdpClient.Receive() 函数的半阻塞效应,如果你做对了,你可以在以后有效地中止该线程.

下面的代码分为三部分。第一部分和最后一部分应分别位于入口和出口点的主循环中。第二部分应该在您创建的新线程中。

一个简单的例子:

// Define this globally, on your main thread
UdpClient listener = null;
// ...


// ...
// Create a new thread and run this code:

IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 9999);
byte[] data = new byte[0];
string message = "";

listener.Client.SendTimeout = 5000;
listener.Client.ReceiveTimeout = 5000;

listener = new UdpClient(endPoint);
while(true)

    try
    
        data = listener.Receive(ref endPoint);
        message = Encoding.ASCII.GetString(data);
    
    catch(System.Net.Socket.SocketException ex)
    
        if (ex.ErrorCode != 10060)
        
            // Handle the error. 10060 is a timeout error, which is expected.
        
    

    // Do something else here.
    // ...
    //
    // If your process is eating CPU, you may want to sleep briefly
    // System.Threading.Thread.Sleep(10);

// ...


// ...
// Back on your main thread, when it's exiting, run this code
// in order to completely kill off the UDP thread you created above:
listener.Close();
thread.Close();
thread.Abort();
thread.Join(5000);
thread = null;

除此之外,您还可以检查UdpClient.Available &gt; 0 以确定在执行UdpClient.Receive() 之前是否有任何UDP 请求排队——这完全消除了阻塞方面。我建议您谨慎尝试,因为此行为未出现在 Microsoft 文档中,但似乎确实有效。

注意:

您在研究此问题时可能发现的MSDN exmaple code 需要额外的user defined class - UdpState。这不是 .NET 库类。这似乎让很多人在研究这个问题时感到困惑。

timeouts 不必严格设置为使您的应用能够完全退出,但它们将允许您在该循环中执行其他操作,而不是永远阻塞。

listener.Close() 命令很重要,因为它强制 UdpClient 抛出异常并退出循环,从而允许处理 Thread.Abort()。如果没有这个,您可能无法正确终止侦听器线程,直到它超时或收到 UDP 数据包导致代码继续通过 UdpClient.Receive() 块。


只是为了补充这个无价的答案,这里有一个工作和测试的代码片段。 (这里是 Unity3D 上下文,但当然适用于任何 c#。)

// minmal flawless UDP listener per PretorianNZ

using System.Collections;
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;

void Start()
   
   listenThread = new Thread (new ThreadStart (SimplestReceiver));
   listenThread.Start();
   

private Thread listenThread;
private UdpClient listenClient;
private void SimplestReceiver()
   
   Debug.Log(",,,,,,,,,,,, Overall listener thread started.");

   IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Any, 1260);
   listenClient = new UdpClient(listenEndPoint);
   Debug.Log(",,,,,,,,,,,, listen client started.");

   while(true)
      
      Debug.Log(",,,,, listen client listening");

      try
         
         Byte[] data = listenClient.Receive(ref listenEndPoint);
         string message = Encoding.ASCII.GetString(data);
         Debug.Log("Listener heard: " +message);
         
      catch( SocketException ex)
         
         if (ex.ErrorCode != 10060)
            Debug.Log("a more serious error " +ex.ErrorCode);
         else
            Debug.Log("expected timeout error");
         

      Thread.Sleep(10); // tune for your situation, can usually be omitted
      
   

void OnDestroy()  CleanUp(); 
void OnDisable()  CleanUp(); 
// be certain to catch ALL possibilities of exit in your environment,
// or else the thread will typically live on beyond the app quitting.

void CleanUp()
   
   Debug.Log ("Cleanup for listener...");

   // note, consider carefully that it may not be running
   listenClient.Close();
   Debug.Log(",,,,, listen client correctly stopped");

   listenThread.Abort();
   listenThread.Join(5000);
   listenThread = null;
   Debug.Log(",,,,, listener thread correctly stopped");
   

【讨论】:

catch 可以在 C# 6 中使用 when contextual keyword 变得更好。像这样:catch (System.Net.Sockets.SocketException ex) when (ex.ErrorCode == 10060)【参考方案2】:

我认为您不应该在循环中使用它,而是在调用 BeginReceive 回调时再次调用 BeginReceive,如果要将数量限制为 100,则保留一个公共变量用于计数。

【讨论】:

【参考方案3】:

先看看 MSDN。他们提供了很好的例子。 http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive.aspx

【讨论】:

好例子?他们甚至懒得提 wtf 是 UdpState(虽然我明白这一点,但想到他们让这个简单的事情溜走仍然让我感到困扰。我想知道他们允许溜走的其他主要事情。) UdpState 定义在第一行(我也错过了)【参考方案4】:

我会在后台线程上进行网络通信,这样它就不会阻塞您应用程序中的任何其他内容。

BeginReceive 的问题是您必须在某个时候调用 EndReceive(否则您有等待句柄) - 并且调用 EndReceive 将阻塞,直到接收完成。这就是为什么将通信放在另一个线程上更容易的原因。

【讨论】:

【参考方案5】:

您必须在其他线程(或task)上执行网络操作、文件操作和依赖于其他事情而不是您自己的程序的事情,因为它们可能会冻结您的程序。原因是您的代码是按顺序执行的。 您已经在一个不好的循环中使用它。每当调用BeginRecieve 回调时,您应该再次调用它。看看下面code:

public static bool messageReceived = false;

public static void ReceiveCallback(IAsyncResult ar)

  UdpClient u = (UdpClient)((UdpState)(ar.AsyncState)).u;
  IPEndPoint e = (IPEndPoint)((UdpState)(ar.AsyncState)).e;

  Byte[] receiveBytes = u.EndReceive(ar, ref e);
  string receiveString = Encoding.ASCII.GetString(receiveBytes);

  Console.WriteLine("Received: 0", receiveString);
  messageReceived = true;


public static void ReceiveMessages()

  // Receive a message and write it to the console.
  IPEndPoint e = new IPEndPoint(IPAddress.Any, listenPort);
  UdpClient u = new UdpClient(e);

  UdpState s = new UdpState();
  s.e = e;
  s.u = u;

  Console.WriteLine("listening for messages");
  u.BeginReceive(new AsyncCallback(ReceiveCallback), s);

  // Do some work while we wait for a message. For this example,
  // we'll just sleep
  while (!messageReceived)
  
    Thread.Sleep(100);
  

【讨论】:

以上是关于如何在循环中使用 UdpClient.BeginReceive的主要内容,如果未能解决你的问题,请参考以下文章

如何在循环中使用 curl PHP 100 次? [关闭]

使用fetch时如何在for循环中动态循环多个promise?

如何在循环中使用python矩阵的维度

如何在 PromiseKit 6 中使用循环?

如何在bash for循环中使用变量[重复]

如何在游戏循环中使用 repaint() 方法