Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C#

Issues with UdpClient.Receive

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
9 Mar 20023 min read 144.4K   1K   33   8
Strange errors I experienced which led me to a major bug in the UdpClient implementation
When more than one UDP packet is enqueued on a socket, UdpClient.Receive will receive packets that are not of the expected size.

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

C#
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.


Written By
Software Developer
Germany Germany
I'm developing for fun since 1985, starting with UCSD Pascal on some old machines (no hard disk, but four floppies!), then moving quickly on to assembler on the famous C64 and Amiga. During university I started professional development for Windows/Unix/Linux, using a myriad of languages (Pi, Assembler (6502, 68000, 80386/486), Cobol, Modula2, Prolog, OML, C, C++, C#, Java, Scala, Groovy, Clojure, VB, Eiffel, Delphi, Perl, Pascal, Javascript). Currently my favorite languages are Clojure, Ruby and modern Javascript.

Comments and Discussions

 
QuestionFixed, now non-issue? Pin
paulpv19-Feb-10 8:09
paulpv19-Feb-10 8:09 
Generalreceive udp message only Pin
yu-yu8-Mar-04 17:12
yu-yu8-Mar-04 17:12 
QuestionSo what are you going to do? Pin
Matt Philmon11-Mar-02 13:14
Matt Philmon11-Mar-02 13:14 
AnswerRe: So what are you going to do? Pin
Christian Rodemeyer12-Mar-02 10:17
professionalChristian Rodemeyer12-Mar-02 10:17 
GeneralRe: So what are you going to do? Pin
Anonymous10-May-03 6:53
Anonymous10-May-03 6:53 
GeneralRe: So what are you going to do? Pin
Anonymous9-Jun-03 23:38
Anonymous9-Jun-03 23:38 
GeneralRe: So what are you going to do? Pin
robdogg113-Aug-04 11:50
robdogg113-Aug-04 11:50 
GeneralThe demo works in the 1.1 framework. Pin
RobotFood8329-Jan-05 13:57
RobotFood8329-Jan-05 13:57 
I just ran the demo with the 1.1 framework and it seems to be working now.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.