65.9K
CodeProject is changing. Read more.
Home

Issues with UdpClient.Receive

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8 votes)

Mar 10, 2002

3 min read

viewsIcon

145897

downloadIcon

1038

Strange errors I experienced which led me to a major bug in the UdpClient implementation

Introduction

Recently, I coded a prototype (for the Cebit 2002) with the release version of Visual Studio.NET. The program had to communicate with an existing server (coded in C++) via a proprietary protocol based on UDP. While doing so, I experienced some strange errors, which finally led me to a major bug in the UdpClient implementation.

Description

When more than one UDP packet is enqueued on a socket, UdpClient.Receive will receive packets with the wrong size. The size of the packet is the sum of the sizes of all packets waiting for receive. The received packet will contain correctly the data of the first waiting packet plus so many null bytes as data is available. If you are working with binary data, you have no possibility to determine the real size of the received packet.

Reproduction

Simply download and execute the UdpClientBug example. It will send four packets to a listening socket, and then receive those packets. You should see the following output:

UdpClient.Receive Bug
Sending 'One' (3 bytes)
Sending 'Two' (3 bytes)
Sending 'Three' (5 bytes)
Sending 'Four' (4 bytes)
Received 15 bytes, s = 'One            ', s.Length = 15
Received 12 bytes, s = 'Two         ', s.Length = 12
Received 9 bytes, s = 'Three    ', s.Length = 9
Received 4 bytes, s = 'Four', s.Length = 4

The first packet received has a size of 15 bytes, which is the sum of all send bytes (3+3+5+4 = 15). When reconverted to strings, the received packets are unequal to packets which were sent.

Explanation

Digging deeper, I have found an explanation of what has gone wrong. The UdpClient.Receive() method returns an array of bytes. The implementation of UdpClient has to create this array with an explicit size before it can call Socket.Receive(). I guess that the implementation uses the property Socket.Available to determine the size to reserve for the array. To cite the documentation, "If you are using a message-oriented Socket type such as Dgram (UDP) the available data is the first message in the input queue.". This is wrong! Socket.Available always returns the number of bytes of all data waiting for receive. To prove this, I have written a second method DemonstrateSocketAvailableBug. The difference is, that now I am using a basic Socket to query the Socket.Available property and receive the data. It produces the following output:

Socket.Available Bug
Sending 'One' (3 bytes)
Sending 'Two' (3 bytes)
Sending 'Three' (5 bytes)
Sending 'Four' (4 bytes)
Available: 15 bytes
Received: 3 bytes, s = 'One', s.Length = 3

I guess that Socket.Available uses ioctlsocket with the FIONREAD option. A quote from the winsock documentation:

FIONREAD returns the amount of data that can be read in a single call to the recv function, which may not be the same as the total amount of data queued on the socket. If s is message oriented (for example, type SOCK_DGRAM), FIONREAD still returns the amount of pending data in the network buffer, however, the amount that can actually be read in a single call to the recv function is limited to the data size written in the send or sendto function call.

Conclusion

In conjunction with the impossibility to set a timeout while receiving data, the UdpClient class is pretty useless for receiving UDP packets. You cannot determine the size of an received packet and if a packet is lost on the network, your code will block for ever. As loss of packets is an Udp 'feature' and you have to deal with this situation. So do yourself a favor, don't use UdpClient, but use the basic socket implementation instead. But be aware, the documentation for the Socket.Available property is wrong too.

Code Example

using System; 
using System.Net; 
using System.Net.Sockets;
using System.Text;

namespace UdpClientBug
{ 
  class Class1 
  {
    [STAThread]
    static void Main(string[] args) 
    {
      Bugs bugs = new Bugs(); 
      bugs.DemonstrateUdpClientBug(); 
      bugs.DemonstrateSocketAvailableBug(); 
      Console.WriteLine(); Console.WriteLine("Press enter to continue ...");
      Console.ReadLine();
    } 
  } 
  
  class Bugs 
  { 
    private IPEndPoint listenerIP = new IPEndPoint(IPAddress.Loopback, 4201); 
    public void DemonstrateUdpClientBug() 
    { 
      Console.WriteLine(); Console.WriteLine("UdpClient.Receive Bug"); 
      
      // Setup listener socket
      UdpClient listener = new UdpClient(listenerIP); // Setup sending socket
      UdpClient sender = new UdpClient(); 
      sender.Connect(listenerIP); 
      
      // Sending  three datagrams to the listener 
      Send(sender, "One"); 
      Send(sender, "Two"); 
      Send(sender, "Three"); 
      Send(sender, "Four"); 
      
      // Now receive the three datagrams from the listener
      Receive(listener);
      Receive(listener);
      Receive(listener); 
      Receive(listener); 
      
      listener.Close(); 
      sender.Close(); 
    }
    
    void Send(UdpClient sender, string s)
    {
      byte[] dgram = Encoding.ASCII.GetBytes(s);
      Console.WriteLine("Sending '" + s + "' (" + dgram.Length.ToString() 
                         + " bytes)");
      sender.Send(dgram, dgram.Length);
    }

    void Receive(UdpClient listener)
    {
      IPEndPoint from = new IPEndPoint(IPAddress.Any, 0);
      byte[] dgram = listener.Receive(ref from);
      string s = Encoding.ASCII.GetString(dgram, 0, dgram.Length);
      Console.WriteLine
      (
        "Received {0} bytes, s = '{1}', s.Length = {2}",
        dgram.Length, s, s.Length 
      );
    }

    public void DemonstrateSocketAvailableBug()
    {
      Console.WriteLine(); Console.WriteLine("Socket.Available Bug");

      Socket listener = new Socket(AddressFamily.InterNetwork, 
                                   SocketType.Dgram, ProtocolType.IP);
      listener.Bind(listenerIP);

      UdpClient sender = new UdpClient();
      sender.Connect(listenerIP);

      // Sending three datagrams to the listener
      Send(sender, "One");
      Send(sender, "Two");
      Send(sender, "Three");
      Send(sender, "Four");

      // Receiving first datagram
      Console.WriteLine("Available: {0} bytes", listener.Available);
      byte[] dgram = new byte[50];
      int nReceived = listener.Receive(dgram);
      string s = Encoding.ASCII.GetString(dgram, 0, nReceived);
      Console.WriteLine("Received: {0} bytes, s = '{1}', 
                        s.Length = {2}", nReceived, s, s.Length);
    }
  }
}

History

  • 10th March, 2002: Initial version

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.