开发者

How to use UdpClient.BeginReceive in a loop

开发者 https://www.devze.com 2023-01-24 02:01 出处:网络
I want to do this for (int i = 0; i < 100; i++ ) { Byte[] receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);

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. 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. Should I use BeginReceive, or just create it under a separate thread?

I consistently get ObjectDisposedException exception. 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.
  2. Start sending/receiving data using 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.


It would seem that UdpClient.BeginReceive() and UdpClient.EndReceive() are not well implemented/understood. And certainly compared to how the TcpListener is implemented, are a lot harder to use.

There are several things that you can do to make using the UdpClient.Receive() work better for you. 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.

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. I do suggest that you try this with caution as this behaviour does not appear in the Microsoft documentation, but does seem to work.

Note:

The MSDN exmaple code you may have found while researching this problem requires an additional user defined class - UdpState. This is not a .NET library class. 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. 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.


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#.)

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


I think you should not use it in a loop but instead whenever the BeginReceive callback is called you call BeginReceive once more and you keep a public variable for count if you want to limit the number to 100.


have look at MSDN first. They provide good example. http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive.aspx


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. 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. 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);
  }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消