简体   繁体   English

如何在循环中使用UdpClient.BeginReceive

[英]How to use UdpClient.BeginReceive in a loop

I want to do this 我想做这个

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

But instead of using UdpClient.Receive , I have to use UdpClient.BeginReceive . 但不是使用UdpClient.Receive ,我必须使用UdpClient.BeginReceive The problem is, how do I do that? 问题是,我该怎么做? There aren't a lot of samples using BeginReceive , and the MSDN example is not helping at all. 使用BeginReceive的示例并不多,MSDN示例根本没有帮助。 Should I use BeginReceive , or just create it under a separate thread? 我应该使用BeginReceive ,还是只在一个单独的线程下创建它?

I consistently get ObjectDisposedException exception. 我一直得到ObjectDisposedException异常。 I only get the first data sent. 我只收到第一个发送的数据。 The next data will throw exception. 下一个数据将抛出异常。

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

What gives? 是什么赋予了?

By the way, the general idea is: 顺便说一下,一般的想法是:

  1. Create tcpclient manager. 创建tcpclient管理器。
  2. Start sending/receiving data using udpclient. 使用udpclient开始发送/接收数据。
  3. When all data has been sent, tcpclient manager will signal receiver that all data has been sent, and udpclient connection should be closed. 当所有数据都已发送后,tcpclient manager将通知接收方已发送所有数据,并且应关闭udpclient连接。

It would seem that UdpClient.BeginReceive() and UdpClient.EndReceive() are not well implemented/understood. 似乎UdpClient.BeginReceive()UdpClient.EndReceive()没有得到很好的实现/理解。 And certainly compared to how the TcpListener is implemented, are a lot harder to use. 当然,与TcpListener的实现方式相比,使用起来要困难得多。

There are several things that you can do to make using the UdpClient.Receive() work better for you. 您可以使用UdpClient.Receive()更好地为您做一些事情。 Firstly, setting timeouts on the underlying socket Client will enable control to fall through (to an exception), allowing the flow of control to continue or be looped as you like. 首先,在底层套接字客户端上设置超时将使控制能够通过(到异常),允许控制流继续或循环播放。 Secondly, by creating the UDP listener on a new thread (the creation of which I haven't shown), you can avoid the semi-blocking effect of the UdpClient.Receive() function and you can effectively abort that thread later if you do it correctly. 其次,通过在新线程上创建UDP侦听器(我没有显示它的创建),可以避免UdpClient.Receive()函数的半阻塞效果,如果你这样做,你可以在以后有效地中止该线程它正确。

The code below is in three parts. 以下代码分为三部分。 The first and last parts should be in your main loop at the entry and exit points respectively. 第一个和最后一个部分应分别位于入口和出口点的主循环中。 The second part should be in the new thread that you created. 第二部分应该在您创建的新线程中。

A simple example: 一个简单的例子:

// 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;

In addition to all this, you can also check UdpClient.Available > 0 in order to determine if any UDP requests are queued prior to executing UdpClient.Receive() - this completely removes the blocking aspect. 除此之外,您还可以检查UdpClient.Available > 0 ,以确定在执行UdpClient.Receive()之前是否有任何UDP请求排队 - 这完全消除了阻塞方面。 I do suggest that you try this with caution as this behaviour does not appear in the Microsoft documentation, but does seem to work. 我建议您谨慎尝试,因为此行为不会出现在Microsoft文档中,但似乎确实有效。

Note: 注意:

The MSDN exmaple code you may have found while researching this problem requires an additional user defined class - UdpState. 您在研究此问题时可能找到的MSDN exmaple代码需要一个额外的用户定义类 - UdpState。 This is not a .NET library class. 这不是.NET库类。 This seems to confuse a lot of people when they are researching this problem. 在研究这个问题时,这似乎让很多人感到困惑。

The timeouts do not strictly have to be set to enable your app to exit completely, but they will allow you to do other things in that loop instead of blocking forever. 不必严格设置超时以使您的应用程序完全退出,但它们将允许您在该循环中执行其他操作而不是永久阻止。

The listener.Close() command is important because it forces the UdpClient to throw an exception and exit the loop, allowing Thread.Abort() to get handled. listener.Close()命令很重要,因为它强制UdpClient抛出异常并退出循环,允许处理Thread.Abort()。 Without this you may not be able to kill off the listener thread properly until it times out or a UDP packet is received causing the code to continue past the UdpClient.Receive() block. 如果没有这个,您可能无法正常终止侦听器线程,直到它超时或收到UDP数据包导致代码继续通过UdpClient.Receive()块。


Just to add to this priceless answer, here's a working and tested code fragment. 只是为了补充这个无价的答案,这是一个经过实践检验的代码片段。 (Here in a Unity3D context but of course for any c#.) (这里是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");
   }

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

I would do network communication on a background thread, so that it doesn't block anything else in your application. 我会在后台线程上进行网络通信,这样它就不会阻止你的应用程序中的任何其他内容。

The issue with BeginReceive is that you must call EndReceive at some point (otherwise you have wait handles just sitting around) - and calling EndReceive will block until the receive is finished. BeginReceive的问题是你必须在某个时候调用EndReceive(否则你只需要等待句柄) - 并且调用EndReceive将阻塞直到接收完成。 This is why it is easier to just put the communication on another thread. 这就是为什么将通信放在另一个线程上更容易的原因。

You have to do network operations, file manipulations and such things that are dependent to other things rather than your own program on another thread (or task ) because they may freeze your program. 您必须在另一个线程(或任务 )上执行网络操作,文件操作以及依赖于其他事物而不是您自己的程序的事情,因为它们可能会冻结您的程序。 The reason for that is that your code executes sequentially. 原因是您的代码按顺序执行。 You have used it in a loop which is not fine. 你已经在一个不好的循环中使用它。 Whenever BeginRecieve callback is invoked you should call it again. 每当调用BeginRecieve回调时,您都应该再次调用它。 Take a look at the following 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);
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM