Issues with UdpClient.Receive





5.00/5 (8 votes)
Mar 10, 2002
3 min read

145897

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 string
s, 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.