Как использовать 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 должно быть закрыто.

person Syaiful Nizam Yahya    schedule 07.11.2010    source источник
comment
Вы, конечно, знаете, что UDP — это протокол, который может терять пакеты и не гарантирует уникальность или порядок, поэтому попытка получить 100 пакетов от определенной конечной точки не обязательно означает, что вы получили те же 100 пакетов в том же порядке, что и были отправлены. ? Возможно, вам следует использовать TCP?   -  person Remus Rusanu    schedule 07.11.2010
comment
я прекрасно это осознаю. причина этого в том, что я хочу проанализировать соединение между двумя сторонами, т. е. оценить пропускную способность.   -  person Syaiful Nizam Yahya    schedule 07.11.2010
comment
сокет beginreceive (асинхронный) в цикле сложно реализовать, вместо этого попробуйте цикл синхронизации с комбинацией NetworkStream.DataAvailable. подсказка: вы можете использовать BeginSend (асинхронно) с тем же сокетом.   -  person sailfish009    schedule 29.07.2018


Ответы (5)


Казалось бы, 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, чтобы определить, поставлены ли какие-либо запросы UDP в очередь перед выполнением UdpClient.Receive() — это полностью устраняет блокирующий аспект. Я предлагаю вам попробовать это с осторожностью, поскольку это поведение не указано в документации Microsoft, но, похоже, работает.

Примечание:

MSDN пример кода, который вы, возможно, обнаружили при исследовании этой проблемы, требует дополнительной определяемый пользователем класс - UdpState. Это не класс библиотеки .NET. Это, кажется, сбивает с толку многих людей, когда они исследуют эту проблему.

Для тайм-аутов не обязательно устанавливать значение позволяют вашему приложению полностью выйти, но они позволят вам делать другие вещи в этом цикле, а не блокировать навсегда.

Команда listener.Close() важна, поскольку она заставляет UdpClient генерировать исключение и выйти из цикла, позволяя обработать Thread.Abort(). Без этого вы, возможно, не сможете правильно завершить поток слушателя, пока не истечет время ожидания или не будет получен пакет UDP, что приведет к продолжению кода после блока UdpClient.Receive().


Чтобы добавить к этому бесценному ответу, вот рабочий и проверенный фрагмент кода. (Здесь в контексте Unity3D, но, конечно, для любого С#.)

// 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");
   }
person PretorianNZ    schedule 19.02.2014
comment
catch можно сделать лучше в C# 6 с помощью when контекстное ключевое слово. Вот так: catch (System.Net.Sockets.SocketException ex) when (ex.ErrorCode == 10060) - person Deantwo; 11.10.2019

Я думаю, что вы не должны использовать его в цикле, но вместо этого всякий раз, когда вызывается обратный вызов BeginReceive, вы вызываете BeginReceive еще раз и сохраняете общедоступную переменную для подсчета, если хотите ограничить число до 100.

person A_Nabelsi    schedule 07.11.2010

сначала посмотрите на MSDN. Они подают хороший пример. http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive.aspx

person Captain Comic    schedule 07.11.2010
comment
Хороший пример? они даже не удосужились упомянуть, что wtf - это UdpState (хотя я это понимаю, меня все еще беспокоит мысль, что они позволили этим простым вещам ускользнуть. Интересно, какую еще важную вещь они упустили.) - person Syaiful Nizam Yahya; 07.11.2010

Я бы сделал сетевое взаимодействие в фоновом потоке, чтобы оно не блокировало что-либо еще в вашем приложении.

Проблема с BeginReceive заключается в том, что в какой-то момент вы должны вызвать EndReceive (в противном случае у вас просто будут сидеть дескрипторы ожидания) - и вызов EndReceive будет блокироваться до тех пор, пока получение не будет завершено. Вот почему проще просто поместить сообщение в другую ветку.

person davisoa    schedule 07.11.2010

Вы должны выполнять сетевые операции, манипулировать файлами и тому подобное, что зависит от других вещей, а не от вашей собственной программы в другом потоке (или task), потому что они могут заморозить вашу программу. Причина этого в том, что ваш код выполняется последовательно. Вы использовали его в цикле, который не подходит. Всякий раз, когда вызывается обратный вызов BeginRecieve, вы должны вызывать его снова. Взгляните на следующий код:

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);
  }
}
person Media    schedule 20.10.2017