Aug. 21, 2010, 11:21 a.m.
posted by vendetta
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.
|
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:
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.
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();
}
}
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. |