2010-11-07 31 views
8

使用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連接。
+0

您知道當然是UDP是可鬆包,並且不保證唯一性,也爲了一個協議,所以試圖從特定端點收到100個數據包並不一定手段你收到相同的100個數據包,按順序發送?也許你應該使用TCP? – 2010-11-07 16:49:51

+0

我完全意識到這一點。原因是因爲我想分析兩方之間的連接,即帶寬估計。 – 2010-11-07 18:05:47

回答

2

我想你不應該使用它在一個循環中,而是每當BeginReceive調用回調函數調用BeginReceive再次和你保持對數的公共變量,如果你想數量限制爲100

2

我會在後臺線程上進行網絡通信,這樣它就不會阻塞應用程序中的其他任何東西。

BeginReceive的問題是,您必須在某個時刻調用EndReceive(否則您只能等待處理) - 並且調用EndReceive將阻塞,直到接收完成。這就是爲什麼將通信放在另一個線程上更容易。

4

看起來UdpClient.BeginReceive()UdpClient.EndReceive()沒有很好的實施/理解。當然,與TcpListener的實現方式相比,使用起來更加困難。

有幾件事情可以使你更好地使用UdpClient.Receive()工作。首先,在底層套接字Client上設置超時將使控制權能夠通過(例外),允許控制流繼續或循環。其次,通過在一個新線程上創建UDP監聽器(創建它我沒有顯示),你可以避免函數的半塊效應,如果你正確地做到了,你可以有效地放棄這個線程。

下面的代碼分爲三部分。第一部分和最後部分應分別在入口點和出口點的主循環中。第二部分應該在你創建的新線程中。

一個簡單的例子:

// 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 exmaple code你可能已經發現了,而研究這個問題需要一個額外的user defined class - UdpState。這不是一個.NET庫類。當他們研究這個問題時,這似乎困擾了很多人。

timeouts不必嚴格設置爲使您的應用程序完全退出,但它們將允許您在該循環中執行其他操作,而不是永久阻止。

listener.Close()命令很重要,因爲它強制UdpClient拋出異常並退出循環,從而允許處理Thread.Abort()。如果沒有這個,你可能無法正確地關閉監聽線程,直到它超時或收到一個UDP包,導致代碼繼續超過UdpClient.Receive()塊。


只是添加到這個無價的答案,這裏是一個工作和測試代碼片段。 (在這裏,在一個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"); 
    } 
+0

謝謝PretorianNZ,無價! – Fattie 2015-04-10 13:53:56

0

你要做的網絡操作,文件操作和這樣的事情是依賴於其他的東西,而不是你自己的程序在另一個線程(或task ),因爲它們可能會凍結你的程序。原因是您的代碼按順序執行。 你已經在一個不好的循環中使用它。每當調用BeginRecieve回調時,應該再次調用它。看看下面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); 
    } 
}