如何在循环中使用UdpClient.BeginReceive

时间:2010-11-07 14:47:54

标签: c# udpclient beginreceive

我想这样做

for (int i = 0; i < 100; i++ )
{
    Byte[] receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);
}

但我不得不使用UdpClient.Receive,而是使用UdpClient.BeginReceive。问题是,我该怎么做?使用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()); 
        }
    }
}

是什么给出了?

顺便说一句,一般的想法是:

  1. 创建tcpclient管理器。
  2. 使用udpclient开始发送/接收数据。
  3. 当所有数据都已发送时,tcpclient管理器将通知接收方已发送所有数据,并且应关闭udpclient连接。

5 个答案:

答案 0 :(得分:6)

似乎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 > 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");
   }

答案 1 :(得分:3)

答案 2 :(得分:2)

我认为你不应该在循环中使用它,但是每当调用BeginReceive回调时,你再次调用BeginReceive,如果你想将数字限制为100,你可以保留一个公共变量。

答案 3 :(得分:2)

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

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

答案 4 :(得分:0)

你必须在另一个线程(或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);
  }
}