A Complete UDP Application



A Complete UDP Application

The preceding sections have discussed several characteristics and quirks of UDP and how to program for them. Each sample program demonstrated a particular UDP programming feature. All those examples are well and good for textbook learning, but they aren’t answers in themselves when you’re trying to create a real-world UDP application.

This section ties together all of the UDP features we’ve discussed and presents a sample UDP application that deploys all the techniques described in this chapter.

Catching Multiple Exceptions by Monitoring Error Codes

So far, you have seen how to catch SocketExceptions when the data buffer is too small for the incoming data and when a set ReceiveTimeout value has expired. But what about when you want to check for both situations in the same program?

Because both events throw the same Exception, you cannot write separate catch blocks to perform specific functions depending on the Exception. Rather, you will have to identify which error produced the Exception. To do this, you must dive into the murky waters of socket error codes and handle them accordingly in your program code. This section describes how to determine what produced a SocketException and how to handle the Exception within the application program code.

When a Socket object throws a SocketException, it is because the underlying WinSock socket has produced some kind of an error condition. The exact error condition can be retrieved from the SocketException object using the ErrorCode property. The ErrorCode property contains the numeric WinSock error message that produced the SocketException. There are lots and lots of WinSock error codes. Figure lists some that you are more likely to encounter in your network programming efforts.

Figure: WinSock Error Codes

Error Code

Description

10004

Interrupted function call

10013

Permission denied

10014

Bad address

10022

Invalid argument

10024

Too many open sockets

10035

Resource temporarily unavailable

10036

Operation now in progress

10037

Operation already in progress

10038

Socket operation on a nonsocket

10039

Destination address required

10040

Message too long

10041

Protocol wrong type for socket

10042

Bad protocol option

10043

Protocol not supported

10044

Socket type not supported

10045

Operation not supported

10046

Protocol family not supported

10047

Address family not supported by protocol family

10048

Address already in use

10049

Cannot assign requested address

10050

Network is down

10051

Network is unreachable

10052

Network dropped connection on reset

10053

Software caused connection abort

10054

Connection reset by peer

10055

No buffer space available

10056

Socket is already connected

10057

Socket is not connected

10058

Cannot send after socket shutdown

10060

Connection timed out

10061

Connection refused

10064

Host is down

10065

No route to host

10067

Too many processes

10091

Network subsystem is unavailable

10101

Graceful shutdown in progress

10109

Class type not found

11001

Host not found

For our purposes here, you are concerned about only two of these error codes:

  • 10040, when the receiving data buffer is too small

  • 10054, when the ReceiveFrom() method times out

To create a method that can monitor both of these error conditions, you must create code for the catch block to check the ErrorCode property of the SocketException object created from the error, and act accordingly. It looks like this:

} catch (SocketException e)
{
  if (e.ErrorCode == 10054)
  {
    // statements to handle ReceiveFrom() timeout
  }
  else if (e.ErrorCode == 10040)
  {
   //statements to handle data buffer overflow
  }
}

By creating an instance (e) of the SocketException object in the catch filter, you can use the ErrorCode property of the instance to check for the exact error code that produced the Exception. Write whatever code is necessary to check for any or all of the specific error conditions. This example checks for the two specific error conditions discussed in this chapter: the ReceiveFrom() time-out and the data buffer overflow. In a real world program, you might want to check for many other things that could go wrong.

Here’s a tip about running a program that produces SocketException errors frequently. You can use the ErrorCode property to help you determine the cause of the SocketException and code for it accordingly. You can even see a text error message of the error code by using the Message property:

} catch (SocketException e)
{
  Console.WriteLine("Socket error {0}: {1}", e.ErrorCode, e.Message)
}

The Complete Client Program

The BestUdpClient.cs program, Listing 6.12, utilizes the AdvSndRecvData() method, which incorporates all of the solutions discussed in this chapter.

Listing 6.12: The BestUdpClient.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class BestUdpClient
{
  private byte[] data = new byte[1024];
  private static IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
  private static EndPoint Remote = (EndPoint)sender;
  private static int size = 30;
  private static int AdvSndRcvData(Socket s, byte[] message,
           EndPoint rmtdevice)
  {
   int recv = 0;
   int retry = 0;
   while (true)
   {
     Console.WriteLine("Attempt #{0}", retry);
     try
     {
      s.SendTo(message, message.Length, SocketFlags.None, rmtdevice);
      data = new byte[size];
      recv = s.ReceiveFrom(data, ref Remote);
     } catch (SocketException e)
     {
      if (e.ErrorCode == 10054)
        recv = 0;
      else if (e.ErrorCode == 10040)
      {
        Console.WriteLine("Error receiving packet");
        size += 10;
        recv = 0;
      }
     }
     if (recv > 0)
     {
      return recv;
     } else
     {
      retry++;
      if (retry > 4)
      {
        return 0;
      }
     }
   }
  }
  public static void Main()
  {
   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 = AdvSndRcvData(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 = AdvSndRcvData(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();
  }
}
End example

The AdvSndRecvDat() method accomplishes several things all in one handy method call:

  • It sends a message out a UDP socket to a remote destination.

  • It waits for a set time to receive a return message.

  • If the return message does not arrive, it sends the message again for a set number of retries.

  • If the return message is larger than the defined data buffer array, it increases the array and sends the original message again, waiting for the response.

You may have noticed that the size variable is now a static variable in the class and not just in the AdvSndRcvData() method. Using the size variable this way allows you to maintain the larger buffer size between calls to the AdvSndRcvData() method. Of course, if you think that your application would only cause one or two large packets, it would still make sense to leave the size parameter in the method, which resets it back to 30 every time.

You can test the BestUdpClient program with the SimpleUdpSrvr program. As I advised for the RetryUdpClient.cs program, you should try various combinations of starting and stopping the server and see how it affects the client program.

Tip 

You can see from this example that it is often best to incorporate small self-contained methods to implement network functions within your programs. This allows you to create detailed methods to check for error conditions for each transmission, without having to repeat the same code every time you need to send or receive a message.

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