Monday, December 10, 2007

Connectionless (UDP) Sockets

Connectionless (UDP) Sockets
Connectionless sockets (the User Datagram Protocol, or UDP) are your other option for transferring data between two networked devices. These are typically used in applications that require little overhead and that want to achieve higher network throughput, such as multimedia streaming protocols. Another advantage in using UDP is that it is capable of transmitting data to multiple endpoints simultaneously because a connection is not bound to a single address. Because UDP transfers datagrams (message packets) instead of a connected stream, these connections are considered unreliable and connectionless. However, don't mistake the term unreliable for low quality—unreliable in this context means only that the protocol does not guarantee that your data packets will ever arrive at your destination. Moreover, there is no sequenced order in which they are guaranteed to arrive, nor any notification if a packet never arrives.

You can compare using UDP datagrams to checking in several pieces of luggage (your packets) at the airport at the same time. Even though they boarded the plane in some order (the packets going out over the network), you're pretty sure that they'll arrive at their destination. Once you get off the plane and attempt to claim your luggage, you're not exactly sure what order they'll be unloaded in (packets arriving at the endpoint), but you can be relatively certain that they'll get there in one piece. Unfortunately, once in a while, something does get lost and never is seen again.

If you are planning to use UDP as your method to send data, it's probably a good idea to have your application either send some sort of acknowledgment that it received a datagram, or provide some way to reassemble packets in a predetermined order by using some sort of sequence—such as a packet number or timestamp—in your datagram message. This can ensure some amount of reliability with the protocol.

Figure 1.4 shows the process for creating both client and server UDP socket connections and how data flows between both network endpoints.

Figure 1.4. Socket process for connectionless clients and servers


Before you can send or receive UDP packets, whether you are the client or the server, you need to create a socket to talk to using the socket() function, and pass the SOCK_DGRAM and IPPROTO_IDP parameters:

SOCKET sUDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

Once you have created your sockets, you also need to bind() the socket to the interface on which you want to receive data. This is done exactly the same way as using a TCP connection.

Now that we have our sockets ready, let's take a look at what is required to both send and receive datagram packets. Remember that even though we have bound the socket, we don't need to call the listen() or accept() functions, as we are going to be operating in a connectionless manner, which enables us to send or receive packets to any device on the network.

Sending a UDP Datagram
Once you have created your sockets, sending a packet over UDP is fairly straightforward. To send a message, you need to call the sendto() function, which is defined as follows:

int sendto (SOCKET s, const char *buf, int len, int flags,
const struct sockaddr *to, int tolen);

You might notice that the parameters are similar to the send() function.

The s parameter is the socket on which we want to send data, which was created using the socket() function. The buf parameter is a pointer to a buffer that contains the data we want to send, and its length is specified in the len parameter. The flags parameter is used to affect the way the data is sent, and can be 0 or MSG_DONTROUTE, which specifies that the data should not be routed. Typically, this parameter will be set to 0. The to parameter contains a pointer to a SOCKADDR_IN address structure with the packet's destination address. You can also construct a broadcast packet (sending it to every machine on the network, which is usually not advised), and you can use the address INADDR_BROADCAST if you have set the socket option to broadcast mode (see the section "Socket Options"). Finally, the tolen parameter specifies the length of the to address.

The sendto() function will return the number of bytes it has transferred, or a SOCKET_ERROR if there was a problem sending the datagram.

The following example sends a UDP datagram:

// Create a connectionless socket
SOCKET sUDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

// Check to see if we have a valid socket
if(sUDPSocket == INVALID_SOCKET) {
int iSocketError = WSAGetLastError();
return FALSE;
}

// Set up the target device address. For this sample,
// we are assuming it is a machine at 192.168.0.1, and on
// port 40040
SOCKADDR_IN sTargetDevice;
memset(&sTargetDevice, 0, sizeof(SOCKADDR_IN));

sTargetDevice.sin_family = AF_INET;
sTargetDevice.sin_port = htons(40040);
sTargetDevice.sin_addr.s_addr = inet_addr("192.168.0.1");

// Send a datagram to the target device
char cBuffer[1024] = "Test Buffer";
int nBytesSent = 0;
int nBufSize = strlen(cBuffer);

nBytesSent = sendto(sUDPSocket, cBuffer, nBufSize, 0,
(SOCKADDR *) &sTargetDevice,
sizeof(SOCKADDR_IN));

// Close the socket
closesocket(sUDPSocket);

Receiving a UDP Datagram
To have your application receive a UDP packet, you need to call the recvfrom() function, which will block until data has arrived from a client (or it can return immediately if in nonblocking mode; see "Socket Options"):

int recvfrom (SOCKET s, char *buf, int len, int flags,
struct sockaddr *from, int *fromlen);

Notice that the parameters are very similar to those described for the recv() function. The first parameter, s, is the socket on which we want to receive data. Next, buf is a pointer to a buffer for the incoming data, and its size is specified by the len parameter. The flags parameter must be set to 0. Finally, the from parameter contains a pointer to the SOCKADDR_IN structure, which contains information about the device that sent the data. A pointer to its length is in the fromlen field.

If the packet is received successfully, recvfrom() will return a 0; otherwise, a SOCKET_ERROR will occur.

The following example shows how to receive a UDP datagram packet:

// Create a connectionless socket
SOCKET sUDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

// Check to see if we have a valid socket
if(sUDPSocket == INVALID_SOCKET) {
int iSocketError = WSAGetLastError();
return FALSE;
}

// Setup a bind on the socket, telling us what port and
// adapter to receive datagrams on. Assume we are listening
// on port 40040
SOCKADDR_IN sReceiveFromAddr;
memset(&sReceiveFromAddr, 0, sizeof(SOCKADDR_IN));

sReceiveFromAddr.sin_family = AF_INET;
sReceiveFromAddr.sin_port = htons(40040);
sReceiveFromAddr.sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(sUDPSocket, (SOCKADDR *)&sReceiveFromAddr,
sizeof(SOCKADDR_IN)) ==
SOCKET_ERROR) {
int iSocketError = WSAGetLastError();
return FALSE;
}

// Receive a datagram from another device
char cBuffer[1024] = "";
int nBytesRecv = 0;
int nBufSize = strlen(cBuffer);
int nReceiveAddrSize = 0;

// Get the datagram
nBytesRecv = recvfrom(sUDPSocket, cBuffer, nBufSize, 0,
(SOCKADDR *) &sReceiveFromAddr,
&nReceiveAddrSize);

// Close the socket
closesocket(sUDPSocket);
WSACleanup();

One final note regarding the sending and receiving of UDP data: You can also transfer data by using the connect(), send(), and recv() functions. Transmitting UDP data this way is considered a somewhat "directed" connectionless transfer, and should be used only if you plan to communicate with one other device during a session (i.e., all packets are sent to the same address). To do this, after your UDP socket has been created, call the connect() function with SOCKADDR_IN set to the machine you want to establish a session with. No real connection will be established, but you can use the send() and recv() functions to transfer data with the associated address.
TCP versus UDP: What Should Your App Use?
Deciding which transport protocol to use can be the hardest part of developing TCP/IP-based applications. The general rule of thumb is to use TCP unless your specific application calls for bandwidth sensitivity or congestion control, which can be relevant issues on Pocket PC devices now that wireless connections are becoming more accessible. UDP should be used in the following situations: when writing applications that require the capability to broadcast data to multiple devices (IP Multicast however, is much less bandwidth-intensive); when writing real-time multimedia applications that can afford to drop packets when congestion occurs; or for extremely small transmissions that require acknowledgment only.

TCP (stream)-based applications are considered much more reliable, and TCP's guarantee of message delivery combined with relatively low overhead makes it an extremely flexible and robust protocol. My best advice is to consider carefully the type of data that you will be sending, as well as your available bandwidth and network conditions, before making your decision.

[ Team LiB ]

Internet Control Message Protocol (ICMP)
The Internet Control Message Protocol (ICMP) is a "support" protocol that is used for sending informational, control, and error messages between network endpoints. It was originally designed to give network routers a way to deliver errors to the network layer of the OSI model so it could decide how to handle the error. ICMP uses a form of IP datagrams known as raw sockets to send information between hosts and servers. Pocket PC, however, does not allow you to explicitly create raw sockets; rather, it uses a set of functions that enable you to send limited ICMP ping messages.

ICMP pinging can be extremely useful when performing functions such as network diagnostics, as it can be used to test network connections, routing, and your TCP/IP stack. To use the ICMP functions, you should include the files icmpapi.h, ipexport.h, and winsock.h in your project's source, as well as link with both the icmplib.lib and winsock.lib libraries.

In order to send an ICMP message, you must first obtain an ICMP handle by calling the following function:

HANDLE IcmpCreateFile (void);

No parameters are required, and an ICMP handle will be returned. If an error has occurred, you will receive an INVALID_HANDLE_VALUE and you can use the GetLastError() function to obtain more information about why it failed.

Once you have a valid ICMP handle, you can send your ICMP request by calling the IcmpSendEcho() function:

DWORD IcmpSendEcho (HANDLE IcmpHandle,
IPAddr DestinationAddress,
LPVOID RequestData, WORD RequestSize,
PIP_OPTION_INFORMATION RequestOptions,
LPVOID ReplyBuffer,
DWORD ReplySize, DWORD Timeout);

For the first parameter, we pass in the handle to the newly created ICMP packet. The DestinationAddress parameter is an unsigned long value specifying the target IP address. You can get this by using one of the IP support functions, such as inet_addr(), described in the section "Name Resolution," later in this chapter. If you want to pass any additional data along with the ICMP message, you can pass a pointer to your buffer using the RequestData parameter, and set its size with the RequestSize field. You will also want to make sure that you don't try to stuff large messages into this buffer, as there is a limit of 8KB on most systems.

The next parameter, RequestOptions, is a pointer to an IP_OPTION_INFORMATION structure:

struct ip_option_information {
unsigned char Ttl;
unsigned char Tos;
unsigned char Flags;
unsigned char OptionsSize;
unsigned char FAR *OptionsData;
};

The IP_OPTION_INFORMATION structure is used to configure your echo request. The Ttl (time to live) parameter determines the amount of time that the packet will be around before it expires. Next, the Tos field specifies the type of service for the ICMP packet, which should be set to 0. The only option that the Flags parameter supports on Pocket PC is IP_FLAG_DF, which instructs ICMP not to fragment the message. Finally, OptionsSize is set to the size of the OptionsData parameter, which is a pointer to any additional options. Currently, Pocket PC supports only IP_OPT_RR, which records the record route; and IP_OPT_TS, which records the timestamp.

The next parameters in IcmpSendEcho() are ReplyBuffer and ReplySize. The ReplyBuffer is a pointer to an array of ICMP_ECHO_REPLY structures that contains responses to our ICMP echo message from each machine to which our request was sent. It is important to ensure that the buffer you have allocated to receive the reply is set to be the size of ICMP_ECHO_REPLY plus eight additional bytes, which are used for any additional error information. The last parameter is Timeout, which is the amount of time IcmpSendEcho() will wait in milliseconds before failing.

IcmpSendEcho() will return the number of packets that are waiting in the ReplyBuffer if it is successful; otherwise, it will return 0. The ICMP_ECHO_REPLY response structure is defined as follows:

struct icmp_echo_reply {
IPAddr Address;
unsigned long Status;
unsigned long RoundTripTime;
unsigned short DataSize;
unsigned short Reserved;
void FAR *Data;
struct ip_option_information Options;
};

Each network node with which we communicated during our echo request will respond with an ICMP_ECHO_REPLY package. The first field contains the Address of the machine that responded. The Status field should return IP_SUCCESS if successful; otherwise, it will contain an error code (defined in ipexport.h). Next, RoundTripTime contains the amount of time in milliseconds that it took for our request to get there. The DataSize field contains the size of the data, returned to us in the Data field. The last field, Options, is an IP_OPTIONS_STRUCTURE containing any options of the returning packet.

Finally, when you are finished sending your echo requests, you can close and clean up your ICMP packets by calling into the IcmpCloseHandle() function, which is defined as follows:

BOOL IcmpCloseHandle(HANDLE IcmpHandle);

The only parameter, IcmpHandle, is the ICMP session handle that you have been using. If ICMP closes successfully, this function will return TRUE. FALSE will be returned to you if an error has occurred.

The following example shows how we can use the ICMP functions to ping another network endpoint to determine whether our Internet connection is active (similar to the desktop ping utility):

// Initialize Winsock
WSADATA wsaData;

memset(&wsaData, 0, sizeof(WSADATA));
if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0)
return FALSE;

// Create a new ICMP message session
HANDLE hIcmpSession = IcmpCreateFile();
if(hIcmpSession == INVALID_HANDLE_VALUE)
return FALSE;

// Convert the target address to a network address
HOSTENT *hostServer = gethostbyname("www.furrygoat.com");
if(hostServer == NULL) {
int dwError = WSAGetLastError();
return FALSE;
}

DWORD dwTargetAddress = *((u_long*)hostServer>h_addr_list[0]);

// Setup the option_information structure
IP_OPTION_INFORMATION ipOptions;
memset(&ipOptions, 0, sizeof(IP_OPTION_INFORMATION));

ipOptions.Ttl = 32;
ipOptions.Flags = IP_FLAG_DF;

// Send our request
BYTE bOutPacket[32];
BYTE bInPacket[1024];

int nTrace = IcmpSendEcho(hIcmpSession, dwTargetAddress, bOutPacket,
sizeof(bOutPacket), &ipOptions, bInPacket, 1024, 5000);
if(nTrace == 0) {
DWORD dwError = GetLastError();
IcmpCloseHandle(hIcmpSession);
return 0;
}

ICMP_ECHO_REPLY *pER = (PICMP_ECHO_REPLY)bInPacket;
for(int i =0;i TCHAR tchOutput[512] = TEXT("\0");
struct in_addr sReplyAddr;
sReplyAddr.S_un.S_addr = (IPAddr)pER->Address;

wsprintf(tchOutput, TEXT("Reply from %hs"),
inet_ntoa(sReplyAddr));
MessageBox(NULL, tchOutput, TEXT("Ping"), MB_OK);
pER++;
}

IcmpCloseHandle(hIcmpSession);


[ Team LiB ]

No comments: