UDP
Highlights
In Chapter 4, we looked at socket programming in .NET, and saw how we can use the Socket class to connect to remote hosts using different protocols. In the last chapter, we looked at the TcpClient and TcpListener classes, which provide a higher-level implementation for connecting over TCP. The Microsoft .NET Framework also provides a special class called UdpClient specifically for implementing the User Datagram Protocol (UDP). In this chapter we'll look at the basics of the UDP protocol, and then see how to use the UdpClient class.
In the previous chapter, we saw the 'three-phase handshake' that TCP uses to ensure that data is transmitted correctly. While this does make TCP far more reliable, it also adds a lot of overhead. UDP has none of this overhead, so it's much faster. This makes it well suited for multi-media transmissions such as video streams, where the precise order that packets arrive in may not be critical.
In fact, UDP is an exceptionally simple protocol-the specification (RFC 768) is only three pages long! (This compares to 85 pages for the TCP specification, RFC 793.)
In this chapter we'll look at:
A basic introduction to UDP
The advantages and disadvantages of the UDP protocol
Implementation of the UDP protocol in .NET using the UdpClient class
Higher-level UDP-based protocols
Overview of the UDP Protocol
The User Datagram Protocol (UDP) is a simple, connection-less, datagram-oriented protocol and provides a fast but not necessarily reliable transport service. It supports and is often used for one-to-many communications, using broadcast or multicast IP datagrams.
Internet Protocol (IP) is the basic protocol of the Internet. Transmission Control Protocol (TCP) and UDP are both transport-level protocols built on top of the underlying IP protocol. The following figure shows how the OSI model maps to the TCP/IP architecture and the TCP/IP protocol suite:
TCP/IP is a suite of protocols, also known as the Internet Protocol Suite, which consists of four layers. Remember TCP/IP is not a single protocol but actually a family or suite of protocols, and consists of other low-level protocols such as IP, TCP, and-the subject of this chapter-UDP. UDP is situated in the transport layer on top of IP (a network-layer protocol). The transport layer provides communications between networks through gateways. It uses IP addresses to send packets of data across the Internet or over a network through various device drivers.
TCP and UDP are part of the TCP/IP suite; each has its own advantages and disadvantages, which we will discuss later in this chapter.
Some UDP Terminology
Before we examine how UDP works, there is some basic terminology that we need to be familiar with. In the following section, we'll briefly define some of the major terms related to UDP.
Packets
In data communication, a packet is a sequence of binary digits, representing data and control signals, which is transmitted and switched across the host. Within the packet, this information is arranged in a specific format.
Datagrams
A datagram is a self-contained, independent packet of data, carrying sufficient data to be routed from source to destination without further information, so no exchanges between the source and destination computers and the transporting network are required.
MTU
MTU stands for Maximum Transmission Unit. The MTU is a characteristic of the link layer that describes the maximum number of bytes of data that can be transferred in a single packet. In other words, the MTU is the largest packet that a given network medium can carry. Ethernet, for example, has a fixed MTU of 1,500 bytes. In UDP, if the size of a datagram is larger than the MTU, IP performs fragmentation, breaking the datagram up into smaller pieces (fragments), so that each fragment is smaller than the MTU.
Ports
UDP uses ports to map incoming data to a particular process running on a computer. UDP routes the packet at the appropriate location using the port number specified in the UDP header of the datagram. Ports are represented by 16-bit numbers, and therefore range from 0 to 65,535. Ports are also referred to as the endpoints of logical connections, and are divided into three categories:
Well-Known Ports-From 0 to 1,023
Registered Ports-From 1,024 to 49,151
Dynamic/Private Ports-49,152 to 65,535
Note that UDP ports can receive more than one message at a time. In some cases, TCP and UDP services may use the same port numbers, such as port 7 (Echo) or port 23 (Telnet).
UDP has the following well-known ports:
UDP Port Number
Description
15
NETSTSAT-Network Status
53
DNS-Domain Name Server
69
TFTP-Trivial File Transfer Protocol
137
NetBIOS Name Service
138
NetBIOS Datagram Service
161
SNMP
The list of UDP and TCP Ports is maintained by IANA (Internet Assigned Numbers Authority). For more information about the associated ports, see http://www.iana.org/assignments/port-numbers.
IP Addresses
The IP datagram consists of 32-bit source and destination IP addresses. The destination IP address specifies the endpoint for the UDP datagram, whereas the source IP address is used to check who sent the message. At the destination, packets are filtered, and those from restricted source IP addresses are discarded, without notifying the sender.
A unicast IP address uniquely identifies a host in a network, whereas a multicast IP address identifies a particular group of hosts in a network. Broadcast IP addresses are received and processed by all the hosts in the local network or in a particular subnet.
IP addresses are divided into five classes, as shown in the table below:
IP class
IP address range
Use
Class A
0.0.0.0 to 127.255.255.255
Networks with a large number of hosts, such as large international organizations
Class B
128.0.0.0 to 191.255.255.255
Networks with a medium number of hosts, such as universities
Class C
192.0.0.0 to 223.255.255.255
Networks with a smaller number of hosts, such as small businesses
Class D
224.0.0.0 to 239.255.255.255
Used for multicast networks, for example online news channels
Class E
240.0.0.0 to 247.255.255.255
Reserved for experimental purposes
A few IP addresses are restricted for special uses:
IP address
Use
0.0.0.0
Default IP address of host (listen on all available interfaces)
127.0.0.1
Local loopback IP address
255.255.255.255
Broadcast IP address for entire local network
TTL
The time-to-live or TTL value allows us to set an upper limit of routers through which a datagram can pass. The TTL value prevents packets from getting caught in routing loops forever. The TTL is initialized by the sender, and the value is decremented by every router that handles the datagram. When the TTL reaches zero, the datagram is discarded.
Multicasting
Multicasting is an open, standards-based method for simultaneously distributing identical information to multiple users. Multicasting is a major feature of the UDP protocol, and is not possible with the TCP protocol. Multicasting allows us to achieve one-to-many communication, for example sending news or mail to multiple recipients, Internet radio, or on-line demo programs. Multicasting is less bandwidth-intensive than broadcasting, because data for multiple users is sent at once:
We'll look at multicasting in detail in the next chapter.
How UDP Works
When a UDP-based application sends data to another networked host, UDP adds an eight-byte header containing the destination and source port number, along with the total length of the data and a checksum. IP adds its own header on top of the UDP datagram to form an IP datagram:
In the previous figure, the total length of the UDP header is specified as eight bytes. Theoretically, the maximum size of an IP datagram is 65,535 bytes. Allowing for an IP header of 20 bytes and a UDP header of 8 bytes, the maximum size for user data is 65,507 bytes. However, most programs use a smaller size than this maximum value. For example, the default size for most applications is around 8,192 bytes because that is the amount of user data that the Network File System (NFS) reads and writes by default. You can set the size of receive and send buffers.
The checksum is used to check whether the data has properly arrived at the destination without being corrupted. The checksum covers both the UDP header and data. A pad byte is used if the checksum of the datagram is odd. If the checksum transmitted is zero, the receiver detects a checksum error and the datagram is discarded. The checksum is optional, but it is recommended always to keep it enabled.
Note that the checksum can't be enabled or disabled using the UdpCllent class. To do this, we need to use the low-level Socket class that we looked at in Chapter 4, and set the NoChecksum option by calling its SetSocketOption() method.
In the next step, the IP layer adds 20 bytes of header that include the TTL, the source and destination IP addresses, along with other information. This is known as IP encapsulation.
As we mentioned above, the maximum size of a packet is 65,507 bytes. If the size of the packet exceeds the default size or the maximum size of the MTU, the IP layer breaks it into segments. These segments are called fragments, and the process of breaking the data into segments is known as fragmentation. The IP header contains all the information about the fragments.
When the sender application ‘throws' a datagram onto the network, the datagram is routed to the destination IP address specified in the IP header. While passing through the router, the TTL (time-to-live) value in the IP header is decreased by one.
When the datagram arrives at the correct destination and port, the IP layer checks whether the datagram is fragmented or not from the IP header. If it is fragmented, the datagram is reassembled using the information available in the header. Finally, the application layer retrieves the filtered data by removing the header.
Disadvantages of UDP
Compared to TCP, UDP has the following disadvantages:
Lack of handshaking signals. Before sending a segment, UDP does not use handshaking signals between sending and receiving the transport layer. The sender therefore has no way of knowing whether the datagram reaches the end system. As a result, UDP cannot guarantee that the data will actually be delivered at the destination (for example, in cases where the end system is off or the network is down).
In contrast, TCP is a connection-oriented service and provides communication between a networked host using packets. TCP uses handshaking signals to check whether the transportation of data was successful.
Use of sessions. To make TCP connection-oriented, sessions are maintained between hosts. TCP uses session IDs to keep track of connections between two hosts. UDP doesn't have any support for sessions due to its connection-less nature.
Reliability. UDP does not guarantee that only one copy of the data will be delivered to the destination. To send large amounts of data to the end system, UDP breaks it into small segments. UDP does not guarantee that these segments will be delivered to the destination in the same order as they were created at the source. In contrast, TCP uses sequence numbers along with port numbers and frequent acknowledgement packets, which guarantee sequenced delivery of data.
Security. TCP is more secure than UDP. In many organizations, firewalls and routers do not allow UDP packets. This is because hackers can use UDP ports, as explicit connections aren't required.
Flow control. UDP doesn't have flow control; as a result, a poorly designed UDP application can tie up a big chunk of network bandwidth.
Advantages of UDP
No connection establishment. UDP is a connection-less protocol, so the overhead of making connections can be avoided. As UDP does not use any handshaking signals, the delay in making connections can be avoided. This is why DNS prefers UDP over TCP-DNS would be much slower if it ran over TCP.
Speed. UDP is fast compared to TCP. Because of this, many applications prefer UDP over TCP. The features that make TCP more robust than UDP (such as handshaking signals) also make it slower.
Topology support. UDP supports both one-to-one and one-to-many connections, whereas TCP supports only one-to-one communication.
Overheads. TCP has higher overhead requirements; UDP has comparatively low overhead requirements. TCP uses substantially more OS resources than UDP does, and as a result UDP is widely used in environments where servers handle many simultaneous clients.
Header size. UDP has only eight-byte headers for every segment, whereas TCP has 20-byte headers, making UDP less consuming of network bandwidth.
The following table summarizes the differences between TCP and UDP:
Characteristics
UDP
TCP
Connection-oriented
N
Y
Use of session
N
Y
Reliability
N
Y
Acknowledgement
N
Y
Sequencing
N
Y
Flow control
N
Y
Secure
Less
More
Data checksum
Optionally
Y
Overhead
Less
More
Speed
Fast
Slower
Topology
One-to-one
One-to-many
One-to-one
Header
8 bytes
20 bytes
When to Use UDP
Many applications on the Internet use UDP. UDP is known as a 'best effort service' protocol. Looking at the advantages and disadvantages of UDP we can conclude that UDP is beneficial:
For broadcasting or multicasting purposes where the application wants to communicate with multiple hosts
Where the datagram size is small and the sequence of fragments is not important
Where connection setup is not required
When the application doesn't want to send important bulk data (as UDP has no flow control)
If retransmission of packets is not required
Where low overhead on the operating system is required
Where network bandwidth is crucial
UDP in .NET
In .NET, the UDP protocol can be implemented using:
The UdpClient class
The Socket class
The Winsock control
The Winsock unmanaged API
The last two of these rely on COM interoperability and P/Invoke respectively, and are not covered in this book. .NET's System.Net.Sockets namespace is essentially a wrapper for the Winsock API, so it is preferable to use the .NET classes. The Winsock control may be a good option for former Visual Basic programmers who want to keep their programming as visual as possible, but it adds the overhead of COM interop, so is better avoided.
We looked at using the Socket class in Chapter 4. Note that this class gives us access to more options than the higher-level UdpClient class (such as the ability to disable the checksum we mentioned earlier), but it does make the code slightly more complex. However, as we'll see shortly, the UdpClient class is built on top of the Socket class, and by inheriting from UdpClient, we can access the underlying Socket.
Therefore, in this chapter we will look at the UDP implementation using the UdpClient class. Before implementing UdpClient, the user must be familiar with some of the other primary classes in .NET, and understand the basics of working with sockets in .NET. These topics were covered in Chapters 4.
The .NET classes for working with UDP reside in the System.Net.Sockets namespace. This namespace provides managed classes for TCP, UDP, and generic sockets programming:
The UdpClient Class
The Microsoft .NET Framework provides the UdpClient class for implementing the UDP protocol on a network. As with the TcpClient and TcpListener classes, this class is built upon the Socket class but hides unnecessary members that aren't required for implementing a UDP-based application.
Using UdpClient is quite simple. First, create an instance of UdpClient. Next, connect to the remote host by calling its Connect() method. These two steps can be achieved in one line of code by specifying the remote IP address and remote port number in the UdpClient's constructor. We said earlier that UDP is a connection-less protocol; so, you might be wondering, why do we need to connect? In fact, the Connect() method does not actually establish a connection to the remote host prior to sending and receiving data. When you send a datagram, the destination needs to be known; the specified IP address and port number serve this purpose.
The third step is to send or receive the data using the Send() or Receive() method. Finally, the Close() method closes the UDP connection. All these steps are illustrated in the following figure:
UdpClient Methods and Properties
The diagram below shows methods and properties of the UdpClient class. The methods and properties inherited from System.Object will not be discussed here. We'll look at these in detail as we discuss using the UdpClient class:
Instantiating a UdpClient
A UdpClient instance can be created in a number of different ways, depending on the parameters passed in. How we use the UdpClient object depends on how it was created.
The simplest way to create a UdpClient instance is to use the default constructor (passing in no parameters). When we use an instance created in this way, we have to either call the Connect() method to establish a connection or specify the connection information when we send data.
// Instantiate UdpClient using the default constructor
UdpClient udpClient = new UdpClient();
If we use this constructor, an arbitrary free port number will be chosen, and the IP address 0.0.0.0 will be used.
We can also create a UdpClient instance specifying a port number as a parameter. In this case, the UdpClient will listen on all local interfaces (that is, it will use the IP address 0.0.0.0). If the port number is not within the range specified by the MinPort and MaxPort fields of the IPEndPoint class, an ArgumentOutOfRangeException (derived from ArgumentException) is thrown. If the port is already in use, a SocketException will be thrown.
// Instantiate UdpClient using port number
try
{
UdpClient UdpClient = new UdpClient(5001);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine("Invalid port number");
}
catch (SocketException e)
{
Console.WriteLine("Port is already in use");
}
The next way is to use an IPEndPoint instance representing the local IP address and port number that we want to use for the connection. The first step here is to instantiate the IPEndPoint; the IPEndPoint instance can be created using a long IP address or an IPAddress object. The IP address must be one of the interfaces of the local machine, or a SocketException will be thrown with the error "The requested address is not valid in its context".
If a null instance of IPEndPoint is passed into the constructor, an ArgumentNullException is thrown.
// Creates an instance of IPEndPoint.
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint ipLocalEndPoint = new IPEndPoint(ipAddress, 5001);
try
{
// Use IPEndPoint instance to create the UdpClient instance
UdpClient udpClient = new UdpClient(ipLocalEndPoint);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
The final way is to pass both a host name and a port number into the constructor. In this case, the constructor is initialized using the host name and port number of the remote host. This allows us to eliminate the step of calling the Connect() method, as this method is called from within this constructor (as you can see if you use ILDasm to look at the IL code for this constructor).
// Instantiate UdpClient using remote host name and a port number.
try
{
UdpClient UdpClient = new UdpClient("remoteHostName", 5001);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Specifying the Connection Information
The second step, once we've created a UdpClient object, is to provide the connection information that is used if we want to send any data to a remote host. Remember that UDP is a connection-less protocol, so this information isn't needed if we only want to receive data-it's only used to indicate where we want to send data to.
This information can be specified in any of three places-we can specify it in the UdpClient constructor, as we've just seen; we can call the UdpClient's Connect() method explicitly; or we can include it in our call to the Send() method, when we actually send the data.
The Connect() method has three overloads:
Using an IPEndPoint instance
Establishing a connection using the IP address and the port number of the remote host
Using a DNS or machine name and the port number of the remote host
Using an IPEndPoint Object
The first overload for Connect() uses an instance of the IPEndPoint class to connect to the remote host, so you should create an IPEndPoint instance before calling the Connect() method. If any error occurs while connecting, a SocketException will be thrown.
// Create instance of UdpClient
UdpClient udpClient = new UdpClient();
// Get the IP address of the remote host
IPAddress ipAddress = IPAddress.Parse("224.56.0.1");
// Create instance of IPEndPoint using IPAddress and port number
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 1234);
try
{
// Connect using this IPEndPoint instance
udpClient.Connect(ipEndPoint);
}
catch (Exception e)
{
Console.WriteLine("Error while connecting: " + e.ToString());
}
Using an IPAddress and Port Number
The second overload takes an IPAddress object and the port number of the remote host. If you know the remote IP address and the remote UDP port, you can make the connection to the remote UDP host as shown in the example below:
// Create instance of UdpClient
UdpClient udpClient = new UdpClient();
// Get the IP address of remote host
IPAddress ipAddress = IPAddress.Parse("224.56.0.1");
try
{
//Connect using created IPAddress instance and remote port
udpClient.Connect(ipAddress, 1234);
}
catch (Exception e)
{
Console.WriteLine("Error while connecting: " + e.ToString());
}
Using the Hostname and Port Number
The final overload uses the DNS or machine name and the port number of the remote host. This method is quite easy, as you do not need to create an IPAddress or IPEndPoint instance:
// Create instance of UdpClient
UdpClient udpClient = new UdpClient();
try
{
udpClient.Connect("remoteMachineName", 1234);
}
catch (Exception e)
{
Console.WriteLine("Error while connecting: " + e.ToString());
}
Sending Data Using the UdpClient
Once we've got a UdpClient instance and (optionally) supplied the connection details, we can start to send data. Unsurprisingly, we do this by calling the Send() method, which is used to send a datagram from the client to the remote host. One important point about the UDP protocol is that it does not receive any type of acknowledgement after sending data to a remote host. Like the Connect() method, Send() has several overloads. Send() returns the length of the data, which can be used to check whether the data was sent properly.
The basic procedure for sending data with the UdpClient class is shown in the following diagram:
The four steps for sending data using UDP are:
Create a UdpClient instance
Connect to the remote host (optional)
Send the data
Close the connection
Before we look at the Send() method in detail, let's take a quick look at an example that shows this general process:
// Example: Sending Data.
private static void Send(string datagram)
{
// Remote IP address
IPAddress remoteAddress = IPAddress.Parse("127.0.0.1");
// Port we want to connect to
int remotePort = 5001;
// ** STEP 1 ** Create the UdpClient instance
UdpClient sender = new UdpClient();
try
{
// ** STEP 2 ** Connect to remote host
sender.Connect(remoteAddress, remotePort);
Console.WriteLine("Sending datagram: {0}", datagram);
byte [] bytes = Encoding.ASCII.GetBytes(datagram);
// ** STEP 3 ** Send data to connected host
sender.Send(bytes, bytes.Length);
// ** STEP 4 ** Close the connection
sender.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
The Send() Method
Although this example shows the general approach, the Send() method can be used in various ways, depending on how the UdpClient was connected to the remote port, and how the UdpClient instance was created. If no connection information has been specified before we call Send(), we need to include this information within the call.
The first overload takes three parameters: the data as an array of bytes, the length of the data as an int, and an IPEndPoint object. To use this overload of Send(), first create an IPEndPoint instance by specifying the end point we want to connect to-the remote IP address and port number. We then pass this IPEndPoint object into the Send() method. Note that we cannot call this overload if we called the Connect() method manually, or if we supplied connection information in the UdpClient constructor. The example below illustrates how to send data using an IPEndPoint:
// Create udpClient instance. Note how instance is created.
UdpClient udpClient = new UdpClient();
// Get the remote IPAddress
IPAddress ipAddress = IPAddress.Parse("148.182.27.1");
// Create instance of IPEndPoint by passing IP address and remote port
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 5005);
// Create data in Byte[] format
byte[] sendBytes = Encoding.ASCII.GetBytes("Wrox UDP Send Example");
try
{
// Send data using the IPEndPoint instance.
udpClient.Send(sendBytes, sendBytes.Length, ipEndPoint);
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.ToString());
}
The second overload allows us to specify the hostname and port number of the remote endpoint, as well as the actual data and its length in bytes. Again, Send() assumes that the connection has not been established between the client and the remote host, so this overload cannot be called if we have already called the Connect() method, or if we specified connection information in the UdpClient constructor.
// Create udpClient instance. Note how instance is created.
UdpClient udpClient = new UdpClient();
// Create data in byte[] format
byte[] sendBytes = Encoding.ASCII.GetBytes("Wrox UDP Send Example");
try
{
// Send data by specifying remote machine name and remote port
udpClient.Send(sendBytes, sendBytes.Length, "remoteHostName", 5001)
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.ToString());
}
The final overload assumes that the UDP client is already connected to the remote host, so it only remains for us to send the data and an int representing the data length using the Send() method. This is the only overload that can be used in conjunction with the Connect() method.
// Create udpClient instance. Note how instance is created.
UdpClient udpClient = new UdpClient("remoteHostName", 5001);
// Create data in Byte[] format
byte[] sendBytes = Encoding.ASCII.GetBytes("WROX UDP Send Example");
try
{
// Send data
udpClient.Send(sendBytes, sendBytes.Length);
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.ToString());
}
Receiving Data Using the UdpClient
To receive data from a remote host via UDP, we naturally enough call the Receive() method. This method takes one reference parameter, an instance of IPEndPoint, and returns the received data as a byte array. It is generally advised to execute this method on a separate thread, since this method polls the underlying socket for incoming datagrams, and blocks until data is received. If this is run on the main thread, your program execution will halt until it receives the datagram packet.
If we've already specified connection information for the UdpClient, either in the constructor or by calling Connect(), only data from the specified remote endpoint will be accepted and returned to our application; connections from other sources will be rejected. If no connection information has been specified, all incoming connections to the local endpoint will be accepted.
After receiving a datagram, the method returns the sent data as a byte array (with the header information stripped off), and populates the IPEndPoint reference parameter with information about the remote host that sent the data.
The process for receiving data from a remote host is very similar to that for sending data:
The general steps for creating a UDP receiver application are:
Create a UdpClient instance
Receive the data
Close the UdpClient
All three steps are shown in the following example. Step 2 occurs when the UdpClient wants to receive data:
// Example: Receiving Data
private static void Receive()
{
// ** STEP 1 ** Create a UdpClient for reading incoming data.
UdpClient receivingUdpClient = new UdpClient(5001); // Listening on port 5001
// Create IPEndPoint variable to pass into Receive() as ref parameter
IPEndPoint RemoteIpEndPoint = null;
try
{
Console.WriteLine("Listening on port 5001…");
// ** STEP 2 ** Without thread, blocks until the socket finishes
// receiving the data
byte[] receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);
// Convert the data
string returnData = Encoding.ASCII.GetString(receiveBytes);
Console.WriteLine("{0} bytes received from {1}",
receiveBytes.Length, RemoteIpEndPoint.ToString());
Console.WriteLine(returnData);
// ** STEP 3 ** Close the UdpClient
receivingUdpClient.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
Closing the Connection
The last step in working with the UdpClient is to close the connection. The Close() method is used to close an open UDP connection. If any error occurs while closing the connection, a SocketException is thrown. Closing a connection is very simple:
udpClient.Close();
Methods for Multicasting
We're not going to look at multicasting in depth in this chapter, as the next chapter is dedicated solely to that topic. However, for completeness' sake, we will mention here the two methods of the UdpClient class that are used for multicasting.
The JoinMulticastGroup() Method
This method is used to join a multicast group. With this method, the UdpClient can receive multicast datagrams broadcast to the specified IP address.
Multicasting allows data to be delivered to multiple destinations. Multicasting is a characteristic of the UDP protocol and widely used in the Internet world.
The JoinMulticastGroup() method allows us to join a multicast IP address. There are two overloads; the first just takes an IPAddress object representing the IP address of the multicast group we want to join. The UdpClient will receive any datagrams broadcast to this IP address:
// Create instance of UdpClient
UdpClient udpClient = new UdpClient();
// IPAddress with multicast ip
IPAddress multicastIP = IPAddress.Parse ("224.123.32.64");
try
{
// Join multicast group
udpClient.JoinMulticastGroup(multicastIP);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
The second overload takes the multicast IP address with the TTL value as an int:
UdpClient udpClient = new UdpClient();
// Create an IPAddress to use to join
IPAddress multicastIP = Dns.Resolve("mutliCastHost").AddressList[0];
try
{
// The life of packet is 30 router hops.
UdpClient.JoinMulticastGroup(multicastIP, 30);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Important To send a multicast datagram, specify an IP multicast address within the range 224.0.0.0 to 239.255.255.255.
The DropMulticastGroup() Method
This method can be used to disconnect a UdpClient from a multicast group. This method takes one parameter-the IP address of the multicast group from which the client is to be dropped:
try
{
// Drop multicast group by sending instance of IPAddress
UdpClient.DropMulticastGroup(multicastIP);
}
catch (Exception e)
{
Console.WriteLine("Error while using DropMulticastGroup :" +
e.ToString());
}
Protected Properties
The UdpClient class has two protected properties, which are accessible from within the class in which it is declared, and from within a derived class. This means that we cannot directly use the UdpClient instance to access the protected properties.
The Active Property
The Active property is used to check the connection with the remote host. This property returns true if the connection is active.
The Client Property
This property is used for retrieving the underlying Socket object that is used by the UdpClient. As we mentioned earlier, the UdpClient class is built on the top of Socket class. The Client property allows us to access the underlying socket, and therefore all the members of the Socket class that are not available through the UdpClient class. This is the biggest advantage of this property. For example, the Blocking property of the Socket class can be used to specify whether the Socket is in blocking mode or not. This cannot be done with the available members of the UdpClient class.
The following example uses both the Active and the Client properties:
// Example: Use of Active and Client property
using System;
using System.Net;
using System.Net.Sockets;
class UdpDerived : UdpClient
{
public void insideSocket()
{
// Uses the protected Active property belonging to the UdpClient base
// class to determine if a connection is established.
if (this.Active)
{
Console.WriteLine("Connection is Active!");
// Retrieve underlying Socket instance to get rich set of all
// methods and properties of Socket class
Socket richSock = this.Client;
// e.g. following Socket property returns socket type you used
Console.WriteLine("Socket Type is: " + richSock.SocketType.ToString());
}
}
[STAThread]
static void Main(string[] args)
{
UdpDerived derivedInstance = new UdpDerived();
// Connect to test Active property
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5001);
// Connect using instance of derived class
derivedInstance.Connect(endPoint);
// Call protected method
derivedInstance.insideSocket();
}
}
Creating a Chat Application Using UDP
In order to provide a more concrete illustration of how these processes tie together, we'll develop a simple chat application in C# using the UdpClient class. Once we've written the send and receive example above, creating a chat application is actually quite simple.
The chat application uses a separate thread to listen for messages from the remote hosts. The Thread class is in the System.Threading namespace, so we will add the following using directive to the project:
using System.Threading;
The application is divided into three logical parts. In the first part, the user is asked to enter information about the local and remote ports and the remote IP address to use. The diagram below shows a possible setup for the ports that allows the application to be tested on a single machine. Port 5001 is used as the sending port for host A, and the receiving port for client B, and vice versa for port 5002:
In the second part, the application listens for incoming data from the remote host. As we discussed earlier, the Receive() method polls for the incoming datagrams and blocks the execution of the thread until a message returns from a remote host. To separate out this process from main flow, a new thread is created. The ThreadStart delegate references the Receiver() method, which is invoked when the thread starts:
Thread tRec = new Thread(new ThreadStart(Receiver));
tRec.Start();
In the Receiver() method, the UdpClient instance is created using the specified localPort:
UdpClient receivingUdpClient = new UdpClient(localPort);
Next, we create a new instance of the IPEndPoint class, and assign it the value null, so that we can pass it into the Receive() method as a reference parameter:
IPEndPoint RemoteIpEndPoint = null;
We then start an infinite while loop in order to receive data using the Receive() method. The data is returned as a byte array, which we convert back into the original string using the Encoding.ASCII class:
while(true)
{
// Wait for datagram
byte[] receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);
// Convert and display data
string returnData = Encoding.ASCII.GetString(receiveBytes);
Console.WriteLine("−" + returnData);
}
The third logical block in the application accepts the data entered by the user and sends it to the specified remote port. This executes on the main thread while the worker thread continues to listen for incoming data. To send data to the remote port, the first step is to create the UdpClient instance:
UdpClient sender = new UdpClient();
Next, the IPEndPoint instance is created using the specified remote IP address and port:
IPEndPoint endPoint = new IPEndPoint(remoteIPAddress, remotePort);
The string data entered by the user is converted into a byte array using the Encoding.ASCII class:
byte[] bytes = Encoding.ASCII.GetBytes(datagram);
Finally, we call the UdpClient's Send() method to send the converted bytes to the remote endpoint:
sender.Send(bytes, bytes.Length, endPoint);
The full code for the chat application is given below:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Wrox.Networking.UDP.ChatApp
{
class Chat
{
private static IPAddress remoteIPAddress;
private static int remotePort;
private static int localPort;
[STAThread]
static void Main(string[] args)
{
try
{
// Get necessary data for connection
Console.WriteLine("Enter Local Port");
localPort = Convert.ToInt16(Console.ReadLine());
Console.WriteLine("Enter Remote Port");
remotePort = Convert.ToInt16(Console.ReadLine());
Console.WriteLine("Enter Remote IP address");
remoteIPAddress = IPAddress.Parse(Console.ReadLine());
// Create thread for listening
Thread tRec = new Thread(new Threads tart(Receiver));
tRec.Start();
while(true)
{
Send(Console.ReadLine());
}
}
catch (Exception e)
{
Console.WriteLine(e,ToString());
}
}
private static void Send(string datagram)
{
// Create UdpClient
UdpClient sender = new UdpClient();
// Create IPEndPoint with details of remote host
IPEndPoint endPoint = new IPEndPoint(remoteIPAddress, remotePort)
try
{
// Convert data to byte array
byte[] bytes = Encoding.ASCII.GetBytes(datagram);
// Send data
sender.Send(bytes, bytes.Length, endPoint);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
// Close connection
sender.Close();
}
}
public static void Receiver()
{
// Create a UdpClient for reading incoming data,
UdpClient receivingUdpClient = new UdpClient(localPort);
// IPEndPoint with remote host information
IPEndPoint RemoteIpEndPoint = null;
try
{
Console.WriteLine(
"-----------*******Ready for chat!!!*******-----------");
while(true)
{
// Wait for datagram
byte[] receiveBytes = receivingUdpClient.Receive(
ref RemoteIpEndPoint);
// Convert and display data
string returnData = Encoding.ASCII.GetString(receiveBytes);
Console.WriteLine("-" + returnData.ToString());
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
And here's the result:
A File Transfer Application
We have seen how to use the UdpClient class to send and receive datagrams, and we created a chat application using the same principle. Next, we'll see how to transfer a file and serialized object using UdpClient. The sender and receiver programs are divided into two logical parts. In the first part, the sender sends file details (namely the file extension and file size) to the receiver(s) as a serialized object, and in the second part the actual file is sent to the destination. In the receiver, the first part accepts the serialized object with the associated information, and in the second part it creates the file on the destination machine. To make the application more interesting, we'll open the saved file using the associated program (for example, a .doc file might be opened with Microsoft Word, or an .htm file with Internet Explorer).
File Server
The file server is a simple console application implemented in a class named FileSender. This has a nested class called FileDetails that contains the information about the file-the file size and the file type. We start by importing the necessary namespaces, and declaring the fields for the class. The class has five private fields-an instance of our FileDetails class, a UdpClient object, plus information about the connection to the remote client, and a FileStream object for reading in the file we'll send to the client:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Xml.Serialization;
using System.Diagnostics;
using System.Threading;
public class FileSender
{
private static FileDetails fileDet = new FileDetails();
// UdpClient-related fields
private static IPAddress remoteIPAddress;
private const int remotePort = 5002;
private static UdpClient sender = new UdpClient();
private static IPEndPoint endPoint;
// Filestream object
private static FileStream fs;
Next we define the FileDetails class. Our FileDetails object will need to be serialized for sending over the network, so we add the [Serializable] attribute. As we've already intimated, the class just has two public fields, to store the type and the size of the file:
// File details (Req. for receiver)
[Serializable]
public class FileDetails
{
public string FILETYPE = "";
public long FILESIZE = 0;
}
Now we come to the Main() method for the server. In this method we invite the user to input a remote IP address to send the file to, and then to enter the path and filename of the file to send. We open up this file with the FileStream object, and check its length. If this is greater than the maximum permitted size of 8,192 bytes, we close the UdpClient and the FileStream, and exit the application. Otherwise, we send the file information, wait two seconds by calling the Thread.Sleep() method, and then send the file itself:
[STAThread]
static void Main(string[] args)
{
try
{
// Get remote IP address and create IPEndPoint
Console,WriteLine("Enter Remote IP address");
remoteIPAddress = IPAddress.Parse(Console.ReadLine() .ToString());
endPoint = new IPEndPoint (remoteIPAddress, remotePort);
// Get file path. (IMP: file size should be less than 8K)
Console.WriteLine("Enter File path and name to send.");
fs = new FileStream(@Console.ReadLine().ToString(), FileMode.Open,
FileAccess.Read);
if (fs.Length > 8192)
{
Console.Write("This version transfers files with size < 8192 bytes");
sender.Close();
fs.Close();
return;
}
SendFileInfo(); // Send file info to receiver
Thread.Sleep(2000); // Wait for 2 seconds
SendFile(); // Send actual file
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
The SendFileInfo() method populates the fields of the FileDetails object, and then serializes this object into a MemoryStream, using an XmlSerializer object. This is then read into a byte array, which is passed into the UdpClient's Send() method, to send the file information to the client:
public static void SendFileInfo()
{
// Get file type or extension
fileDet.FILETYPE = fs.Name.Substring((int)fs.Name.Length − 3, 3);
// Get file length (Future purpose)
fileDet.FILESIZE = fs.Length;
XmlSerializer fileSerializer = new XmlSerializer(typeof(FileDetails));
MemoryStream stream = new MemoryStream();
// Serialize object
fileSerializer.Serialize(stream, fileDet);
// Stream to byte
stream.Position = 0;
byte[] bytes = new byte[stream.Length];
stream.Read(bytes, 0, Convert.ToInt32(stream.Length))
Console.WriteLine("Sending file details…");
// Send file details
sender.Send(bytes, bytes.Length, endPoint);
stream.Close();
}
The SendFile() method just reads the file content from the FileStream into a byte array, and then sends this to the client:
private static void SendFile()
{
// Creating a file stream
byte[] bytes = new byte[fs.Length];
// Stream to bytes
fs.Read(bytes, 0, bytes.Length);
Console.WriteLine("Sending file…size = " + fs.Length + "bytes");
try
{
sender.Send(bytes, bytes.Length, endPoint); // Send file
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
// Clean up
fs.Close();
sender.Close();
}
Console.Read();
Console.WriteLine("File sent successfully.");
}
}
File Receiver
The file receiver is again a console application, and is implemented in a class named FileRecv. Again, we start by importing the necessary namespaces and declaring the fields for the class:
using System;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using System.Xml.Serialization;
public class FileRecv
{
private static FileDetails fileDet;
// UdpClient vars
private static int localPort = 5002;
private static UdpClient receivingUdpClient = new UdpClient(localPort);
private static IPEndPoint RemoteIpEndPoint = null;
private static FileStream fs;
private static byte[] receiveBytes = new byte{0];
We will need to deserialize the file information sent from the server into a FileDetails object, so we need to define that class within the client application, too:
[Serializable]
public class FileDetails
{
public string FILETYPE = "";
public long FILESIZE = 0;
}
The Main() method for the application just calls two methods, to get respectively the file details and the file itself:
[STAThread]
static void Main(string[] args)
{
// Get the file details
GetFileDetails();
// Receive file
ReceiveFile();
}
The GetFileDetails() method calls the Receive() method of the UdpClient object. This receives the serialized FileDetails object from the server, which we save to a MemoryStream. We use an XmlSerializer object to deserialize this stream back into a FileDetails object, and display the retrieved information in the console:
private static void GetFileDetails()
{
try
{
Console.WriteLine(
"-----------*******Waiting to get File Details!!*******-----------");
// Receive file info
receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);
Console.WriteLine("----Received File Details!!");
XmlSerializer fileSerializer = new XmlSerializer(typeof(FileDetails))
MemoryStream stream1 = new MemoryStream();
// Received byte to stream
stream1.Write(receiveBytes, 0, receiveBytes.Length);
stream1.Position = 0; // IMP
// Call the Deserialize method and cast to the object type.
fileDet = (FileDetails)fileSerializer.Deserialize(stream1);
Console.WriteLine ("Received file of type ." + fileDet.FILETYPE +
" whose size is " + fileDet.FILESIZE.ToString () + " bytes");
}
catch (Exception e)
{
Console.WriteLine (e.ToString ());
}
}
The ReceiveFile() method retrieves the file from the server and saves it to disk with the filename temp, plus the extension retrieved from the FileDetails object. We then call the Process.Start() static method to open the document with the associated program:
public static void ReceiveFile()
{
try
{
Console.WriteLine(
"-----------*******Waiting to get File!!*******-----------");
// Receive file
receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);
// Convert and display data
Console.WriteLine("-----File received…Saving…");
// Create temp file from received file extension
fs = new FileStream("temp." + fileDet.FILETYPE, FileMode.Create,
FileAccess.ReadWrite, FileShare.ReadWrite);
fs.Write(receiveBytes, 0, receiveBytes.Length);
Console.WriteLine("----File Saved…");
Console.WriteLine("-------Opening file with associated program------);
Process.Start(fs.Name); // Opens file with associated program
}
catch (Exception e)
{
Console.WriteLine(e.ToString ());
}
finally
{
fs.Close();
receivingUdpClient.Close();
}
}
}
Here's the output on the client and server when we run this program:
This application has some limitations; firstly, the size of the file depends upon the size of the internal message buffer or network limit. The default buffer size is 8,192 bytes, so we cannot send files larger than 8,192 bytes. This could be overcome by dividing the file into multiple segments, each with a buffer size of 8,192 bytes. By calling the Read() method with the required buffer size, we can divide the file into multiple segments.
As UDP does not use acknowledgement signals, we would have to implement a separate mechanism to check whether each segment was received correctly or not before sending the next segment. This can be achieved by creating another instance of UdpClient in both the sender and receiver, which will check for acknowledgement messages.
Broadcasts
When we run this application, we can specify an individual IP address to send the file to, but we can also specify a broadcast address, to send the file to all machines on the subnet, or on the entire network. A broadcast address consists of the subnet ID, with all remaining bits set to 1. For example, if we want to broadcast a message to all hosts with IP addresses in the range 192.168.0, we would use the broadcast address 192.168.0.255. To send a message to all machines on the network, regardless of the subnet mask, we can therefore use the address 255.255.255.255. With broadcasts, the message is sent to every machine on the network; it is for the client to decide whether or not it wants to process the data.
We'll discuss broadcasts in a bit more detail in the next chapter, where we look at multicasting. Unlike broadcasts, multicast connections can cross over a firewall. They are also less bandwidth-intensive, and are therefore generally recommended in preference to broadcasts. We will therefore concentrate on multicasting rather than broadcasts in the next chapter.
Higher-level UDP-based Protocols
UDP is useful where the order of delivery is not important and does not need to be guaranteed. As the sender does not know which destination is active, it uses a port number to specify the type of service required from the remote host.
In the Internet world of today, there are many applications that use UDP services, including Internet phone, Internet video conferencing, and real-time audio/video broadcasting. Most people don't know that DCOM's preferred transport protocol is UDP. The connection-less nature of UDP allows DCOM to perform several optimizations by merging many low-level acknowledgement packages with actual data and pinging messages. This minimizes network round trips as much as possible, and therefore improves speed and performance. Microsoft networking uses UDP for logon, browsing, and name resolution.
Some of the higher-level protocols based on UDP are:
Application-layer protocol
Description/Use
RTF (Real-time Protocol)
Real time media
NFS (Network File System)
Remote file server
SNMP (Simple Network Management Protocol)
Network management
RIP (Routing Information Protocol)
Routing protocol
DNS (Domain Name Service)
Hostname resolution
TFTP (Trivial File Transfer Protocol)
File transfer application
RPC (Remote Procedure Call)
Typical client-server model
LDAP (Lightweight Directory Access Protocol)
Directory services
Real-time Protocol (RTP)
RTP is an application-layer protocol designed for delivering real-time media such as audio/video over unicast or multicast private and public IP networks. One example is Microsoft NetMeeting, which uses RTP to send real-time information across the Internet.
The RTP-based application implements RTP by using UDP protocol and by adding some functionality. The added functionality provides sequence numbering, payload identification, source identification, and time-stamping.
Network File System (NFS)
Another popular application that uses UDP is Network File System, which provides transparent file access to files and file-systems across the network. The advantage of NFS over FTP (File Transfer Protocol) is that it provides transparent access to files. That is, NFS can access the portion of the file that is referenced by an application or process.
NFS is built using Sun RFC, and uses the reserved UDP port number 2049 to perform file operations.
Simple Network Management Protocol (SNMP)
As its name indicates, Simple Network Management Protocol (SNMP) is a network-management protocol widely used in networks. SNMP allows administrators to monitor and control remote hosts and gateways on a network. The SNMP service can handle one or more requests from the host. It communicates between a management program run by an administrator and the network management agent running on a host. SNMP uses ports 161 and 162 for the manager and agent respectively.
Domain Name Service (DNS)
The Domain Name Service is a distributed database used by TCP/IP applications to map hostnames to IP addresses. UDP is the preferred protocol for DNS applications, and it uses port 53 to send DNS queries to a name server.
Trivial File Transfer Protocol (TFTP)
TFTP is an application-layer protocol useful for transferring files between remote hosts. This protocol is intended to be used when bootstrapping diskless systems. It uses UDP port number 69 for its file transfer activity.