2010-11-07 11 views
8

Ich möchte dieseWie UdpClient.BeginReceive in einer Schleife verwenden

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

tun, sondern stattdessen UdpClient.Receive zu verwenden, ich habe UdpClient.BeginReceive zu verwenden. Das Problem ist, wie mache ich das? Es gibt nicht viele Beispiele, die BeginReceive verwenden, und das MSDN-Beispiel hilft überhaupt nicht. Soll ich BeginReceive verwenden oder einfach unter einem separaten Thread erstellen?

Ich bekomme konsequent ObjectDisposedException Ausnahme. Ich bekomme nur die ersten Daten gesendet. Die nächsten Daten werden eine Ausnahme auslösen.

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

Was gibt?

By the way, die allgemeine Idee ist:

  1. tcpclient Manager erstellen.
  2. Starten Sie das Senden/Empfangen von Daten mit udpclient.
  3. Wenn alle Daten gesendet wurden, signalisiert der Tcpclient Manager dem Empfänger, dass alle Daten gesendet wurden, und die Verbindung zum udpclient sollte geschlossen werden.
+0

Sie wissen natürlich, dass UDP ist ein Protokoll, das Pakete verlieren kann und nicht die Einzigartigkeit noch Ordnung nicht garantieren, so versuchen, 100 Pakete von einem bestimmten Endpunkt erhalten nicht unbedingt Mittel dass Sie die gleichen 100 Pakete in der Reihenfolge erhalten haben, die gesendet wurden? Vielleicht solltest du TCP benutzen? –

+0

Ich bin mir dessen vollkommen bewusst. Der Grund dafür ist, weil ich die Verbindung zwischen zwei Parteien analysieren möchte, dh die Bandbreitenschätzung. –

Antwort

2

Ich denke, man sollte es nicht in einer Schleife verwenden, sondern immer dann, wenn die BeginReceive Rückruf Sie rufen genannt wird BeginReceive noch einmal und Sie halten eine öffentliche Variable für Zählung, wenn Sie die Nummer 100.

2
begrenzen möchten

haben zuerst MSDN. Sie bieten ein gutes Beispiel.

+15

Gutes Beispiel? Sie erwähnen nicht einmal, dass wtf UdpState ist (obwohl ich herausfinde, es stört mich immer noch zu denken, dass sie diese einfachen Dinge zu gleiten lassen. Ich frage mich, was andere wichtige Sache, die sie ausschlüpfen konnten.) –

2

Ich würde Netzwerk-Kommunikation auf einem Hintergrund-Thread, so dass es nichts anderes in Ihrer Anwendung blockiert. Das Problem mit BeginReceive ist, dass Sie EndReceive an einem bestimmten Punkt aufrufen müssen (sonst haben Sie Warte-Griffe nur herum sitzen) - und Aufruf von EndReceive wird blockiert, bis der Empfang beendet ist. Deshalb ist es einfacher, die Kommunikation einfach auf einen anderen Thread zu legen.

4

Es scheint, dass UdpClient.BeginReceive() und UdpClient.EndReceive() nicht gut umgesetzt sind. Und sicherlich verglichen mit der Implementierung des TcpListeners, ist es viel schwieriger zu verwenden.

Es gibt mehrere Dinge, die Sie tun können, um die Verwendung der UdpClient.Receive() Arbeit besser für Sie zu machen. Erstens ermöglicht das Setzen von Timeouts auf dem zugrunde liegenden Socket-Client, dass die Steuerung durchläuft (zu einer Ausnahme), wodurch der Steuerungsfluss fortgesetzt oder beliebig geloopt werden kann. Zweitens, durch Erstellen des UDP-Listeners in einem neuen Thread (dessen Erstellung ich nicht gezeigt habe), können Sie den Semi-Blocking-Effekt der UdpClient.Receive()-Funktion umgehen und Sie können diesen Thread später effektiv abbrechen, wenn Sie es richtig machen.

Der folgende Code besteht aus drei Teilen. Der erste und der letzte Teil sollten sich in der Hauptschleife an den Ein- und Ausstiegspunkten befinden. Der zweite Teil sollte in dem neuen Thread sein, den Sie erstellt haben.

Ein einfaches Beispiel:

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

Neben all das können Sie auch UdpClient.Available > 0 überprüfen, um festzustellen, ob UDP-Anforderungen in der Warteschlange werden vor UdpClient.Receive() zur Ausführung - diese vollständig entfernt die Blockierung Aspekt.Ich schlage vor, dass Sie dies mit Vorsicht versuchen, da dieses Verhalten nicht in der Microsoft-Dokumentation angezeigt wird, aber scheint zu funktionieren.

Hinweis:

Die MSDN exmaple code Sie gefunden haben, während dieses Problem untersucht erfordert eine zusätzliche user defined class - UdpState. Dies ist keine .NET-Bibliotheksklasse. Dies scheint eine Menge Leute zu verwirren, wenn sie dieses Problem erforschen.

Die timeouts müssen nicht unbedingt so eingestellt werden, dass Ihre App vollständig beendet wird, aber sie ermöglichen Ihnen, andere Dinge in dieser Schleife zu tun, anstatt für immer zu blockieren.

Der Befehl listener.Close() ist wichtig, da er den UdpClient zwingt, eine Ausnahme auszulösen und die Schleife zu beenden, sodass Thread.Abort() verarbeitet werden kann. Ohne diese Option können Sie den Listener-Thread möglicherweise nicht ordnungsgemäß beenden, bis das Zeitlimit überschritten wird oder ein UDP-Paket empfangen wird, das dazu führt, dass der Code über den UdpClient.Receive() - Block hinaus fortgesetzt wird.


Nur auf diese unschätzbare Antwort hinzuzufügen, hier ist ein Arbeits- und getestet Codefragment. (Hier in einem Unity3D Kontext aber natürlich für jeden 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

danke PretorianNZ, unbezahlbar! – Fattie

0

Sie müssen Netzwerkoperationen tun, Dateimanipulationen und solche Dinge, die zu anderen Dingen abhängig sind und nicht Ihr eigenes Programm auf einem anderen Thread (oder task) weil sie Ihr Programm einfrieren können. Der Grund dafür ist, dass Ihr Code sequenziell ausgeführt wird. Sie haben es in einer Schleife verwendet, die nicht in Ordnung ist. Immer wenn der Rückruf aufgerufen wird, sollten Sie ihn erneut anrufen. Werfen Sie einen Blick auf die folgende 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); 
    } 
} 
Verwandte Themen