关于c#:如何在循环中使用UdpClient.BeginReceive

How to use UdpClient.BeginReceive in a loop

我想这么做

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

但我不使用UdpClient.Receive,而是使用UdpClient.BeginReceive。问题是,我该怎么做?使用BeginReceive的样本并不多,而msdn示例根本没有帮助。我应该使用BeginReceive还是只在单独的线程下创建它?

我一直得到ObjectDisposedException例外。我只收到第一批发送的数据。下一个数据将引发异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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());
        }
    }
}

给出了什么?

顺便说一下,总的想法是:

  • 创建TCPClient管理器。
  • 开始使用udpclient发送/接收数据。
  • 当所有数据都已发送时,TCPClient Manager将向接收器发送所有数据已发送的信号,并且应关闭UDPClient连接。

  • 似乎UdpClient.BeginReceive()UdpClient.EndReceive()没有得到很好的实施/理解。当然,与tcplistener的实现方式相比,使用起来要困难得多。

    有几件事你可以做,使使用UdpClient.Receive()更好地为你工作。首先,在底层套接字客户机上设置超时将使控制权落入(异常),允许控制流继续或按您的喜好循环。其次,通过在一个新线程上创建udp侦听器(我还没有展示创建的线程),可以避免UdpClient.Receive()函数的半阻塞效应,如果您做得正确,可以在以后有效地中止该线程。

    下面的代码分为三部分。第一部分和最后一部分应该分别位于入口和出口点的主循环中。第二部分应该在您创建的新线程中。

    一个简单的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // 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以确定在执行UdpClient.Receive()之前是否有任何UDP请求排队—这完全消除了阻塞方面。我建议您谨慎地尝试此操作,因为此行为不会出现在Microsoft文档中,但似乎确实有效。

    注:

    您在研究此问题时可能发现的msdn-exmaple代码需要一个额外的用户定义类udpstate。这不是.NET库类。这似乎让很多人在研究这个问题时感到困惑。

    不必严格设置超时以使应用程序完全退出,但它们将允许您在该循环中执行其他操作,而不是永远阻塞应用程序。

    listener.close()命令很重要,因为它强制udpclient引发异常并退出循环,从而允许处理thread.abort()。如果没有这个,在侦听器线程超时或接收到UDP包导致代码继续通过udpclient.receive()块之前,您可能无法正确地终止该线程。

    为了增加这个无价的答案,这里有一个正在工作和测试的代码片段。(这里是Unity3D的上下文,当然是任何C。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    // 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");
       }


    先看一下msdn。它们提供了很好的例子。http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive.aspx


    我会在后台线程上进行网络通信,这样它就不会阻塞应用程序中的任何其他内容。

    BeginReceive的问题是,您必须在某个时间点调用EndReceive(否则,您的等待句柄就在附近),调用EndReceive将阻塞,直到接收完成。这就是为什么只将通信放在另一个线程上更容易的原因。


    我认为您不应该在循环中使用它,而应该在调用BeginReceive回调时再次调用BeginReceive,如果您希望将该数限制为100,则将一个公共变量保留为Count。


    您必须在另一个线程(或任务)上执行网络操作、文件操作以及依赖于其他事物而不是您自己的程序的事情,因为它们可能冻结您的程序。原因是代码按顺序执行。你在一个不好的循环中使用它。每当调用BeginRecieve回调时,您应该再次调用它。请看下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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);
      }
    }