When UDP Goes Bad



When UDP Goes Bad

While solving the message boundary problem found in TCP communications, UDP introduces some other predicaments that programmers must deal with in UDP programs:

  • Lost data as a result of how the ReceiveFrom() method works

  • Detecting and allowing for lost packets

These two UDP issues often cause unexpected dilemmas for network programmers who are more accustomed to working with TCP communications, and you must take them into consideration when creating production-quality UDP applications. This section gives you the tools you need to understand and work around these events when writing your C# network programs.

Preventing Lost Data

One of the advantages of TCP communication is the internal TCP buffer. As seen in Chapter 5, all data sent by the TCP socket is placed in an internal buffer area before being sent out on the network. Likewise, all data received on the socket is placed in the internal buffer area before being read by the Receive() method. When the Receive() method tries to read data from the buffer, if not all of the data is read, the remainder stays in the buffer and waits for the next Receive() call. This is not the case, however, with UDP communication.

UDP does not have to worry about packet retransmissions and therefore does not use a buffer system. All data sent from the socket to the network is immediately sent on the network. Likewise, all data received from the network is immediately forwarded to the next ReceiveFrom() method call. This system produces an important and potentially critical problem that is often overlooked by network programmers still learning their craft: data not immediately read from the UDP socket is lost forever.

When the ReceiveFrom() method is used in a program, the programmer must ensure that the data buffer specified is large enough to accept all the data from the UDP socket. If the buffer is too small, data will be lost. You can observe this unfortunate turn of events by modifying the SimpleUDpClient program from Listing 6.2 to use a smaller data buffer. The result is Listing 6.6, BadUdpClient.cs.

Listing 6.6: The BadUdpClient.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class BadUdpClient
{
  public static void Main()
  {
   byte[] data = new byte[30];
   string input, stringData;
   IPEndPoint ipep = new IPEndPoint(
           IPAddress.Parse("127.0.0.1"), 9050);
   Socket server = new Socket(AddressFamily.InterNetwork,
           SocketType.Dgram, ProtocolType.Udp);
   string welcome = "Hello, are you there?";
   data = Encoding.ASCII.GetBytes(welcome);
   server.SendTo(data, data.Length, SocketFlags.None, ipep);
   IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
   EndPoint tmpRemote = (EndPoint)sender;
   data = new byte[30];
   int recv = server.ReceiveFrom(data, ref tmpRemote);
   Console.WriteLine("Message received from {0}:", tmpRemote.ToString());
   Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
   while(true)
   {
     input = Console.ReadLine();
     if (input == "exit")
      break;
     server.SendTo(Encoding.ASCII.GetBytes(input), tmpRemote);
     data = new byte[30];
     recv = server.ReceiveFrom(data, ref tmpRemote);
     stringData = Encoding.ASCII.GetString(data, 0, recv);
     Console.WriteLine(stringData);
   }
   Console.WriteLine("Stopping client");
   server.Close();
  }
}

The BadUdpClient program defines the data buffer as being only 30 bytes:

byte[] data = new byte[30];

After compiling the program, you can test it by running it with the SimpleUdpSrvr program. At first, things seem just fine. BadUdpClient sends the standard greeting message to the server, and receives the server’s welcome banner. You can test out a few short phrases, and the program will work as expected. But try a phrase longer than 30 characters, and watch the fireworks. Take a look at the following:

C:\>BadUdpClient
Message received from 127.0.0.1:9050:
Welcome to my test server
test message
test message
longer test message
longer test message
This is an even longer test message
Unhandled Exception: System.Net.Sockets.SocketException: A message sent on a
datagram socket was larger than the internal message buffer or some other
network limit, or the buffer used to receive a datagram into was smaller
than the datagram itself
  at System.Net.Sockets.Socket.ReceiveFrom(Byte[] buffer, Int32 offset, Int32
   size, SocketFlags socketFlags, EndPoint& remoteEP)
  at System.Net.Sockets.Socket.ReceiveFrom(Byte[] buffer, EndPoint& remoteEP)
  at BadUdpClient.Main()
C:\>
End example

When the longest test message was entered, the application produced an Exception, which of course was not accommodated in BadUdpClient and caused all sorts of difficulties. The underlying event is the ReceiveFrom() method’s detecting that more data was available to read than would fit into the size of the supplied buffer. So it tried to notify the program by throwing a SocketException. This is a handy notification that more data was available than what you were able to accept, and that data is now lost.

Although you can never retrieve the original lost data, you can compensate for when this situation occurs. By placing a try-catch block around the ReceiveFrom() method, you can detect when data has been lost because of a small buffer and attempt to modify the data buffer size to accommodate the next time the application tries to receive the data. The BetterUdpClient.cs program, shown in Listing 6.7, demonstrates this technique.

Listing 6.7: The BetterUdpClient.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class BetterdUdpClient
{
  public static void Main()
  {
   byte[] data = new byte[30];
   string input, stringData;
   IPEndPoint ipep = new IPEndPoint(
           IPAddress.Parse("127.0.0.1"), 9050);
   Socket server = new Socket(AddressFamily.InterNetwork,
           SocketType.Dgram, ProtocolType.Udp);
   string welcome = "Hello, are you there?";
   data = Encoding.ASCII.GetBytes(welcome);
   server.SendTo(data, data.Length, SocketFlags.None, ipep);
   IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
   EndPoint tmpRemote = (EndPoint)sender;
   data = new byte[30];
   int recv = server.ReceiveFrom(data, ref tmpRemote);
   Console.WriteLine("Message received from {0}:", tmpRemote.ToString());
   Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
   int i = 30;
   while(true)
   {
     input = Console.ReadLine();
     if (input == "exit")
      break;
     server.SendTo(Encoding.ASCII.GetBytes(input), tmpRemote);
     data = new byte[i];
     try
     {
      recv = server.ReceiveFrom(data, ref tmpRemote);
      stringData = Encoding.ASCII.GetString(data, 0, recv);
      Console.WriteLine(stringData);
     } catch (SocketException)
     {
      Console.WriteLine("WARNING: data lost, retry message.");
      i += 10;
     }
   }
   Console.WriteLine("Stopping client");
   server.Close();
  }
}
End example

Instead of using a fixed data-buffer array size, the BetterUdpClient program uses a variable that can be set to a different value each time the ReceiveFrom() method is used:

data = new byte[i];
try
{
  recv = server.ReceiveFrom(data, ref tmpRemote);
  stringData = Encoding.ASCII.GetString(data, 0, recv);
  Console.WriteLine(stringData);
} catch (SocketException)
{
  Console.WriteLine("WARNING: data lost, retry message.");
  i += 10;
}

If a SocketException occurs, you know that the data overflowed the available data buffer in the ReceiveFrom() method. This program warns the customer of the event and increases the size of the data buffer by a set amount. All future calls to ReceiveFrom() will use the larger data buffer size. If another large message is received, the data buffer will be increased again. The output should look something like this:

C:\>BetterUdpClient
Message received from 127.0.0.1:9050:
Welcome to my test server
test
test
This is an even longer test message
WARNING: data lost, retry message.
This is an even longer test message
This is an even longer test message
This is a really, really, really long test message
WARNING: data lost, retry message.
This is a really, really, really long test message
This is a really, really, really long test message
exit
Stopping client
C:\>

During the test, I entered a phrase longer than the 30-byte buffer. Sure enough, the SocketException occurred, and the warning message was displayed. To ensure that the data buffer was increased, I retyped the same message to see if it would be accepted. As expected, it worked. This demonstrates a simple way to avoid losing data on UDP sockets.

Note 

Most real-world applications use a geometrical technique to increase the buffer size so retransmissions are kept to a minimum. This could include doubling the buffer size with each large packet received.

In this section, we examined the importance of using a data buffer that is adequate but no larger than what is necessary to support the data in the application. This helps the program control memory usage. As an alternative, you can always use ridiculously large data buffers so that you never run out of room—but be prepared for your application to be a memory hog.

Preventing Lost Packets

The other difficult task often encountered with UDP communications is providing for the possibility of lost packets. Because UDP is a connectionless protocol, there is no way for a device to know if a packet it sends actually made it to the remote device. For some UDP applications, this is not an issue. Many games, for instance, use UDP packets to transmit positional information for players in the game. Every few seconds, a player’s position and game status are transmitted to other players in the game. If a packet is lost in the network, an updated one will automatically be sent a few seconds later.

For other applications, however, packet loss can cause great difficulty. When you are trying to retrieve network management data from a device using the Simple Network Management Protocol (SNMP), you expect to get a response from the device. If you don’t, you must compensate within your application.

The simplest thing to do to account for lost packets is to devise an arrangement similar to TCP’s retransmission system. Packets sent successfully to a remote host should generate return packets from the remote device. If the expected return packet is not received within a specific amount of time, the original packet can be retransmitted.

There are two techniques that you can use to implement a UDP retransmission scheme:

Asynchronous sockets and a Timer object This technique requires the use of an asynchronous socket that can listen for incoming packets without blocking. After the socket is set to do an asynchronous read, a Timer object can be set. If the Timer object goes off before the asynchronous read finishes, a retransmission is in order. This topic is covered in detail in Chapter 8, "Asynchronous Socket Programming."

Synchronous sockets and setting a socket time-out value For this arrangement, you use the SetSocketOption() method as discussed in the next section.

Using Socket Time-outs

As seen in Chapter 3, the ReceiveFrom() method is a blocking function. It will block execution of the program until it receives a data packet. This is a Very Bad Thing in UDP programs because you are not guaranteed to receive a packet. If a data packet never arrives, your program is stuck forever, or at least until the customer gets aggravated and manually stops it.

By default, the ReceiveFrom() method blocks for infinity, waiting for data. You can control this, however, by using socket options. You can control how long ReceiveFrom()waits, and you can allow it to abort its wait. The SetSocketOption() method offers various socket options on a created Socket object, and one of these is the ReceiveTimeout option. This sets the amount of time the socket will wait for incoming data before signaling a time-out.

The format of the SetSocketOption() method is as follows:

SetSocketOption(SocketOptionLevel so, SocketOptionName sn, int value)

The SocketOptionLevel specifies what type of socket option to implement. The SocketOptionName defines the specific option to set, and the last parameter (int value) indicates the set value for the option.

To designate the ReceiveTimeout option, which is a socket-level option, you must use the following format:

server.SetSocketOption(SocketOptionLevel.Socket,
 SocketOptionName.ReceiveTimeout, 3000);

Because SetSocketOption() is a method in the Socket class, you can only use it on an active Socket object (called server in the preceding example). The integer parameter defines the amount of time (in milliseconds) that the socket will wait for data before triggering a time-out.

Setting the Time-out

You can see the time-out problem occur by running the SimpleUdpClient program without the SimpleUdpSrvr program running. It will wait for the server to return data—which of course won’t happen. (You will have to manually stop the client program by pressing Ctrl-C.)

Let’s add a few lines to the SimpleUdpClient program to set the time-out value. In addition to setting the socket time-out option, the Listing 6.8 also uses the GetSocketOption() to show that the time-out value did really change.

Listing 6.8: The TimeoutUdpClient.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class TimeoutUdpClient
{
  public static void Main()
  {
   byte[] data = new byte[1024];
   string input, stringData;
   int recv;
   IPEndPoint ipep = new IPEndPoint(
           IPAddress.Parse("127.0.0.1"), 9050);
   Socket server = new Socket(AddressFamily.InterNetwork,
           SocketType.Dgram, ProtocolType.Udp);
   int sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout);
   Console.WriteLine("Default timeout: {0}", sockopt);
   server.SetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout, 3000);
   sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout);
   Console.WriteLine("New timeout: {0}", sockopt);
   string welcome = "Hello, are you there?";
   data = Encoding.ASCII.GetBytes(welcome);
   server.SendTo(data, data.Length, SocketFlags.None, ipep);
   IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
   EndPoint tmpRemote = (EndPoint)sender;
   data = new byte[1024];
   recv = server.ReceiveFrom(data, ref tmpRemote);
   Console.WriteLine("Message received from {0}:", tmpRemote.ToString());
   Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
   while(true)
   {
     input = Console.ReadLine();
     if (input == "exit")
      break;
     server.SendTo(Encoding.ASCII.GetBytes(input), tmpRemote);
     data = new byte[1024];
     recv = server.ReceiveFrom(data, ref tmpRemote);
     stringData = Encoding.ASCII.GetString(data, 0, recv);
     Console.WriteLine(stringData);
   }
   Console.WriteLine("Stopping client");
   server.Close();
  }
}
End example

The TimeoutUdpClient program first gets the original ReceiveTimeout value from the socket and displays it, then sets it to three seconds:

int sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
 SocketOptionName.ReceiveTimeout);
Console.WriteLine("Default timeout: {0}", sockopt);
server.SetSocketOption(SocketOptionLevel.Socket,
 SocketOptionName.ReceiveTimeout, 3000);

The GetSocketOption() method returns an Object object, so it must be typecast to an integer to see the actual value. After compiling the program, you can test it by executing it without the SimpleUdpSrvr program running. The output should look like this:

C:\>TimeoutUdpClient
Default timeout: 0
New timeout: 3000
Unhandled Exception: System.Net.Sockets.SocketException: An existing connection
was forcibly closed by the remote host
  at System.Net.Sockets.Socket.ReceiveFrom(Byte[] buffer, Int32 offset, Int32
size, SocketFlags socketFlags, EndPoint& remoteEP)
  at System.Net.Sockets.Socket.ReceiveFrom(Byte[] buffer, EndPoint& remoteEP)
  at TimeoutUdpClient.Main()
C:\>

As you can see, the original ReceiveTimeout value was set to zero, which indicates it will wait indefinitely for data. After adding the SetSocketOption() method to set it to 3000 milliseconds, the GetSocketOption() method returns 3000.

After displaying the ReceiveTimeout value, the program waits for incoming data for about three seconds on the ReceiveFrom() method. Then the program produces a SocketException and blows up.

The setting for the socket ReceiveTimeout option lasts for the duration of the socket. You can test this by starting the SimpleUdpSrvr program and then running the TimeoutUdpClient program. After the normal exchange of greetings, you can send a few test messages and then stop the SimpleUdpSrvr program (using Ctrl-C). Next, try and send a message from the client. Again, after about three seconds, an exception occurs.

When the ReceiveTimeout value has been reached, the socket stops waiting for data and produces a SocketException. For the time-out feature to help your program, you must write code to catch the SocketException. This task is described next.

Catching the Exception

Now that you know the socket will produce a SocketException when the time-out is reached, you need only to catch the Exception so you can gracefully inform the customer about it and allow some alternatives to the application. The ExceptionUdpClient.cs program (Listing 6.9) performs this function.

Listing 6.9: The ExceptionUdpClient.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class ExceptionUdpClient
{
  public static void Main()
  {
   byte[] data = new byte[1024];
   string input, stringData;
   int recv;
   IPEndPoint ipep = new IPEndPoint(
           IPAddress.Parse("127.0.0.1"), 9050);
   Socket server = new Socket(AddressFamily.InterNetwork,
           SocketType.Dgram, ProtocolType.Udp);
   int sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout);
   Console.WriteLine("Default timeout: {0}", sockopt);
   server.SetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout, 3000);
   sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout);
   Console.WriteLine("New timeout: {0}", sockopt);
   string welcome = "Hello, are you there?";
   data = Encoding.ASCII.GetBytes(welcome);
   server.SendTo(data, data.Length, SocketFlags.None, ipep);
   IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
   EndPoint Remote = (EndPoint)sender;
   data = new byte[1024];
   try
   {
     recv = server.ReceiveFrom(data, ref Remote);
     Console.WriteLine("Message received from {0}:", Remote.ToString());
     Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
   } catch (SocketException)
   {
     Console.WriteLine("Problem communicating with remote server");
     return;
   }
   while(true)
   {
     input = Console.ReadLine();
     if (input == "exit")
      break;
     server.SendTo(Encoding.ASCII.GetBytes(input), ipep);
     data = new byte[1024];
     try
     {
      recv = server.ReceiveFrom(data, ref Remote);
      stringData = Encoding.ASCII.GetString(data, 0, recv);
      Console.WriteLine(stringData);
     } catch (SocketException)
     {
      Console.WriteLine("Error receiving message");
     }
   }
   Console.WriteLine("Stopping client");
   server.Close();
  }
}
End example

In ExceptionUdpClient.cs, the socket ReceiveTimeout is set to 3000 milliseconds as before. All of the ReceiveFrom() method calls are placed within a try-catch block to catch the SocketException that is raised if no data appears within the ReceiveTimeout value:

try
{
  recv = server.ReceiveFrom(data, ref tmpRemote);
  Console.WriteLine("Message received from {0}:", tmpRemote.ToString());
  Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
} catch (SocketException)
{
  Console.WriteLine("Problem communicating with remote server");
  return;
}

If no data is received before expiration of the time-out, a SocketException occurs, and the statements in the catch block inform the customer that there was no response from the remote device. The specific behavior of the catch block depends on where in the program the error occurs. If, at the start of the communication, no packet is received from the server, it is assumed that the server is not present, and the program is terminated. During the message exchange section, a single packet gone missing is not a reason to terminate the program. Instead, a warning message is displayed on the console, and the receive loop is continued.

Running the ExceptionUdpClient program with no accompanying server program produces the following result:

C:\>ExceptionUdpClient
Default timeout: 0
New timeout: 3000
Problem communicating with remote server
C:\>

Now, that’s a much kinder way to deal with the situation!

Warning 

While testing the time-out programs on a Windows XP Professional workstation, I noticed an odd behavior. When connecting to either the local IP address or the loopback address, the socket time-out value did not work properly. However, when connecting to a remote IP address, it worked as expected—yet another reason to test your network applications in a real-world environment.

If the SimpleUdpSrvr is running initially but is stopped after a few messages are passed, the output looks like this:

C:\>ExceptionUdpClient
Default timeout: 0
New timeout: 3000
Message received from 127.0.0.1:9050:
Welcome to my test server
test message
test message
Second test message
Second test message
Third test message
Error receiving message
Last test message
Error receiving message
exit
Stopping client
C:\>

In this case, the missing messages in the message loop are identified, but the customer still has control and can try sending more messages or terminate the program with the exit command.

This Exception-catching technique watched all the ReceiveFrom() methods for success and informed the customer if any of them failed. A nice feature, but it doesn’t really solve the problem, which was that the original message did not make it to the destination. The preferred situation would be for the program to try resending the original message. This is the topic of the next section.

Handling Retransmissions

There are lots of reasons why a UDP packet can’t make it to its destination. Many of those reasons are only temporary difficulties, and a second or third attempt at sending the same packet might be successful. These further attempts are called retries. Most production-quality UDP applications allow for a set number of retries before finally giving up on the communication.

If you are programming in a UDP environment where you must send a message and receive a return message, and no return message is received, you can use retries to resend the original message multiple times. When a return messagedoes eventually come, you can go on with the rest of your program. If no answer is ever received, however, you must indicate the problem to your customer and offer a solution.

Creating a Retransmission Setup

The simplest way to accomplish the retransmission arrangement is to create a separate method in the class to handle all sending and receiving of messages. The steps in this method would look something like this:

  1. Send a message to a remote host.

  2. Wait for an answer from the remote host.

  3. If an answer is received, accept it and exit the method with the received data and the size of the data.

  4. If no answer is received within a time-out value, increment a retry value.

  5. Check the retry value. If it is less than the number of retries desired, go to step 1 and start over. If it is equal, abort the retransmission attempt and report the results to the customer.

Once the generic class method for sending and receiving UDP packets is created, it can be used everywhere in the program where a message is sent to a remote host and a response is expected. In C# code, the method would look like Listing 6.10.

Listing 6.10: The SndRcvData() method
Start example
int SndRcvData(Socket s, byte[] message, EndPoint rmtdevice)
{
  int recv;
  int retry = 0;
  while (true)
  {
   Console.WriteLine("Attempt #{0}", retry);
   try
   {
     s.SendTo(message, message.Length, SocketFlags.None, rmtdevice);
     data = new byte[1024];
     recv = s.ReceiveFrom(data, ref rmtdevice);
   } catch (SocketException)
   {
     recv = 0;
   }
   if (recv > 0)
   {
     return recv;
   } else
   {
     retry++;
     if (retry > 4)
     {
      return 0;
     }
   }
  }
}
End example

This method requires three parameters:

  • A Socket object that has already been established by the calling program

  • The data message to send to the remote host

  • An EndPoint object that points to the IP address and port of the remote host

The Socket object passed to the new method should already be established as a UDP socket and have the ReceiveTimeout SocketOption value set to a reasonable amount of time to wait for an answer on the particular network.

The SndRcvData() method first attempts to send the message to the remote host using the standard SendTo() method. After sending the message, SndRcvData() blocks on the ReceiveFrom() method, waiting for a return message. If a message is received from the remote host within the ReceiveTimeout value, SndRcvData() places it in a global data buffer defined in the class as a byte array and returns the number of bytes received. The calling program can read the received message from the global data buffer, using the size returned from the SndRcvData() method.

If no message is returned by the ReceiveTimeout value, a SocketException occurs and the catch block takes effect. In the catch block, the recv value is set to zero. After the try-catch block, the recv value is tested. A positive result means a message was successfully received. A zero result means no message was received, so a retry value is incremented and then checked to see whether it has reached the desired maximum value. If it hasn’t, the whole process is repeated, starting with resending the message.

If the retry value has reached its preset maximum value, the SndRcvData() method returns a zero. The calling application can then detect the zero value and act accordingly.

Using the Method in a Program

The RetryUdpClient.cs program , shown in Listing 6.11, implements the new message retransmission technique to communicate with a remote UDP echo server.

Listing 6.11: The RetryUdpClient.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class RetryUdpClient
{
  private byte[] data = new byte[1024];
  private static IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
  private static EndPoint Remote = (EndPoint)sender;
  private int SndRcvData(Socket s, byte[] message, EndPoint rmtdevice)
  {
   int recv;
   int retry = 0;
   while (true)
   {
     Console.WriteLine("Attempt #{0}", retry);
     try
     {
      s.SendTo(message, message.Length, SocketFlags.None, rmtdevice);
      data = new byte[1024];
      recv = s.ReceiveFrom(data, ref Remote);
     } catch (SocketException)
     {
      recv = 0;
     }
     if (recv > 0)
     {
      return recv;
     } else
     {
      retry++;
      if (retry > 4)
      {
        return 0;
      }
     }
   }
  }
  public RetryUdpClient()
  {
   string input, stringData;
   int recv;
   IPEndPoint ipep = new IPEndPoint(
           IPAddress.Parse("127.0.0.1"), 9050);
   Socket server = new Socket(AddressFamily.InterNetwork,
           SocketType.Dgram, ProtocolType.Udp);
   int sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout);
   Console.WriteLine("Default timeout: {0}", sockopt);
   server.SetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout, 3000);
   sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
    SocketOptionName.ReceiveTimeout);
   Console.WriteLine("New timeout: {0}", sockopt);
   string welcome = "Hello, are you there?";
   data = Encoding.ASCII.GetBytes(welcome);
   recv = SndRcvData(server, data, ipep);
   if (recv > 0)
   {
     stringData = Encoding.ASCII.GetString(data, 0, recv);
     Console.WriteLine(stringData);
   } else
   {
     Console.WriteLine("Unable to communicate with remote host");
     return;
   }
   while(true)
   {
     input = Console.ReadLine();
     if (input == "exit")
      break;
     recv = SndRcvData(server, Encoding.ASCII.GetBytes(input), ipep);
     if (recv > 0)
     {
      stringData = Encoding.ASCII.GetString(data, 0, recv);
      Console.WriteLine(stringData);
     } else
      Console.WriteLine("Did not receive an answer");
   }
   Console.WriteLine("Stopping client");
   server.Close();
  }
  public static void Main()
  {
   RetryUdpClient ruc = new RetryUdpClient();
  }
}
End example

The RetryUdpClient program uses the SndRcvData() method for all communication with the remote UDP server. As usual, a greeting message is first sent to the remote host. The Main() method knows if a message has been received by the SndRcvData() method by testing the returned value:

recv = SndRcvData(server, data, ipep);
if (recv > 0)
{
  stringData = Encoding.ASCII.GetString(data, 0, recv);
  Console.WriteLine(stringData);
} else
{
  Console.WriteLine("Unable to communicate with remote host");
  return;
}

If the returned value is greater than zero, it means a message has been received. The global data buffer data contains the received message, and the returned value represents the length of the received message. If the returned value is zero, no message was received from the remote server, and the program acts accordingly.

The message loop uses the SndRcvData() method to send the message entered on the console and wait for the response. Here, as well, the message loop must contain code to check the return value from the SndRcvData() method and determine whether a message was received from the remote host. If not, a message informing the customer must be displayed.

Testing the Program

Now comes the fun part—testing to make sure that the newly created retransmission method will work. The first test is to see if it works with no server present at all. Run the RetryUdpClient program without the accompanying UDP server:

C:\>RetryUdpClient
Default timeout: 0
New timeout: 3000
Attempt #0
Attempt #1
Attempt #2
Attempt #3
Attempt #4
Unable to communicate with remote host
C:\>

As expected, the retry feature started and tried five times to send the greeting message to the server. After the fifth attempt, it returned a zero to the Main() method, which in turn exited the program. If you are testing this on a network, you can use WinDump or Analyzer to watch the retry packets go out. Each retry attempt generates a separate greeting packet. The time between packets should be close to the time-out value set in the program.

Next, you can test to see if the retry method works by simulating lost packets on the network. Do this by starting the RetryUdpClient program and letting it go through a couple of retry loops; then start the SimpleUdpSrvr program on the target machine. The retry loop should detect the server and pick up as if nothing ever happened:

C:\>RetryUdpClient
Default timeout: 0
New timeout: 3000
Attempt #0
Attempt #1
Attempt #2
Attempt #3
Attempt #4
Welcome to my test server
This is a test message.
Attempt #0
This is a test message.
Here's another message.
Attempt #0
Here's another message.
exit
Stopping client
C:\>

This test responded as expected. After four unsuccessful attempts to receive data from the server, the last attempt successfully sent the greeting message to the server and received the welcome banner in return. After that, the normal activity took place.

As a final test, you can stop the server program in the middle of the message transmissions to see what happens. Start the SimpleUdpSrvr program on the designated server machine and start the RetryUdpClient program on its designated machine. Everything should work normally (unless, of course, you have real dropped packets). After sending a couple of test messages, stop SimpleUdpSrvr by pressing Ctrl-C. Next, try to send a message from the RetryUdpClient program. You should see the following:

C:\>RetryUdpClient
Default timeout: 0
New timeout: 3000
Attempt #0
Welcome to my test server
First test message
Attempt #0
First test message
Second test message
Attempt #0
Second test message
Third test message
Attempt #0
Attempt #1
Attempt #2
Attempt #3
Attempt #4
Did not receive an answer
Last test message
Attempt #0
Attempt #1
Attempt #2
Attempt #3
Attempt #4
Did not receive an answer
exit
Stopping client
C:\>

Again, as expected, the client program communicated with the remote server, sending the greeting message and receiving the welcome banner. The first two messages were sent just fine, as indicated by the return message. Then, when the server program was stopped, another test message was sent. After five retries, the program gave up and displayed the designated error message. The program was still active and was able to attempt to send another message from the customer, but again it failed and another error message was displayed.

Using this retransmission technique, you can create robust UDP applications that can withstand normal network congestion problems and get data to the remote host.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows