2010-11-07 6 views
8

voglio fare questoCome utilizzare UdpClient.BeginReceive in un ciclo

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

Ma invece di usare UdpClient.Receive, devo usare UdpClient.BeginReceive. Il problema è, come faccio? Non ci sono molti campioni che usano BeginReceive e l'esempio MSDN non aiuta affatto. Dovrei usare BeginReceive, o semplicemente crearlo in un thread separato?

ho sempre ottengo ObjectDisposedException eccezione. Ricevo solo i primi dati inviati. I prossimi dati genereranno un'eccezione.

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

Cosa dà?

Tra l'altro, l'idea generale è:

  1. Crea direttore TcpClient.
  2. Inizia l'invio/ricezione di dati tramite classe UdpClient.
  3. Quando tutti i dati è stato inviato, TcpClient gestore segnalerà ricevitore che tutti i dati sono stati inviati, e collegamento classe UdpClient dovrebbe essere chiusa.
+0

Siete consapevoli, naturalmente, che UDP è un protocollo che può perdere i pacchetti e non garantisce l'unicità, né ordine, in modo da tentare di ricevere 100 pacchetti da uno specifico endpoint non necessariamente mezzi che hai ricevuto gli stessi 100 pacchetti, in ordine, che sono stati inviati? Forse dovresti usare il TCP? –

+0

ne sono perfettamente consapevole. la ragione di questo è perché, voglio analizzare la connessione tra 2 parti, cioè la stima della larghezza di banda. –

risposta

2

penso che non si dovrebbe usare in un ciclo, ma invece ogni volta che il callback BeginReceive si chiama si chiama BeginReceive ancora una volta e si mantiene un variabile pubblica per il conteggio se si vuole limitare il numero a 100.

2

Farei la comunicazione di rete su un thread in background, in modo che non blocchi nient'altro nella vostra applicazione.

Il problema con BeginReceive è che è necessario chiamare EndReceive ad un certo punto (altrimenti si hanno maniglie di attesa appena sedute) e chiamare EndReceive verrà bloccato fino al termine della ricezione. Questo è il motivo per cui è più semplice mettere la comunicazione su un altro thread.

4

Sembrerebbe che UdpClient.BeginReceive() e UdpClient.EndReceive() non sono ben implementate/capire. E certamente rispetto a come viene implementato TcpListener, è molto più difficile da usare.

ci sono diverse cose che si possono fare per rendere l'utilizzo il lavoro UdpClient.Receive() meglio per voi. Innanzitutto, impostando i timeout sul socket sottostante, Client abiliterà il controllo a passare (ad un'eccezione), consentendo al flusso di controllo di continuare o essere loopato come desiderato. In secondo luogo, creando il listener UDP su un nuovo thread (la cui creazione non è stato mostrato), è possibile evitare l'effetto di semi-blocco della funzione UdpClient.Receive() e si può interrompere in modo efficace quel thread in seguito se lo si fa correttamente.

Il codice seguente è in tre parti. La prima e l'ultima parte dovrebbero essere nel tuo ciclo principale rispettivamente ai punti di entrata e di uscita. La seconda parte dovrebbe essere nel nuovo thread che hai creato.

Un semplice esempio:

// 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 aggiunta a tutto questo, è anche possibile controllare UdpClient.Available > 0 al fine di determinare se le richieste UDP sono in coda prima di eseguire UdpClient.Receive() - questo elimina completamente l'aspetto di blocco.Suggerisco di provare questo con cautela poiché questo comportamento non appare nella documentazione di Microsoft, ma sembra funzionare.

Nota:

Il MSDN exmaple code potrebbe essere trovato durante la ricerca di questo problema richiede un ulteriore user defined class - UdpState. Questa non è una classe di libreria .NET. Questo sembra confondere un sacco di persone quando stanno studiando questo problema.

Il timeouts non deve essere impostato rigorosamente per consentire all'app di uscire completamente, ma ti permetterà di fare altre cose in quel ciclo invece di bloccarlo per sempre.

Il comando listener.Close() è importante perché obbliga UdpClient a generare un'eccezione e ad uscire dal ciclo, consentendo a Thread.Abort() di essere gestito. In caso contrario, potrebbe non essere possibile eliminare correttamente il thread del listener fino a quando non si verifica un timeout o se viene ricevuto un pacchetto UDP che provoca il proseguimento del codice oltre il blocco UdpClient.Receive().


Solo per aggiungere a questa risposta inestimabile, ecco un frammento di codice funzionante e testato. (Qui in un contesto Unity3D ma naturalmente per qualsiasi 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"); 
    } 
+0

grazie a PretorianNZ, impagabile! – Fattie

0

Devi fare operazioni di rete, le manipolazioni di file e queste cose che sono dipendenti ad altre cose piuttosto che il proprio programma su un altro thread (o task) perché potrebbero bloccare il tuo programma. Il motivo è che il tuo codice viene eseguito in sequenza. Lo hai usato in un ciclo che non va bene. Ogni volta che viene richiamata la richiamata BeginRecieve, è necessario richiamarla di nuovo. Date un'occhiata al seguente 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); 
    } 
}