Connectionless Protocols

Connectionless protocols behave differently than connection-oriented protocols, so the method for sending and receiving data is substantially different. First we'll discuss the receiver (or server, if you prefer) because the connectionless receiver requires little change when compared with the session-oriented servers. Following that we'll look at the sender.

Receiver

For a process to receive data on a connectionless socket, the steps are simple. First create the socket with either socket or WSASocket. Next bind the socket to the interface on which you wish to receive data. This is done with the bind function (exactly like the session-oriented example). The difference with connectionless sockets is that you do not call listen or accept. Instead, you simply wait to receive the incoming data. Because there is no connection, the receiving socket can receive datagrams originating from any machine on the network. The simplest of the receive functions is recvfrom, which is defined as

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

The first four parameters are the same as recv, including the possible values for flagsMSG_OOB and MSG_PEEK. The same warnings for using the MSG_PEEK flag also apply to connectionless sockets. The from parameter is a SOCKADDR structure for the given protocol of the listening socket, with fromlen pointing to the size of the address structure. When the API call returns with data, the SOCKADDR structure is filled with the address of the workstation that sent the data.

The Winsock 2 version of the recvfrom function is WSARecvFrom. The prototype for this function is

 int WSARecvFrom( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, struct sockaddr FAR * lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE ); 

The difference is the use of WSABUF structures for receiving the data. You can supply one or more WSABUF buffers to WSARecvFrom with dwBufferCount indicating this. By supplying multiple buffers, scatter-gather I/O is possible. The total number of bytes read is returned in lpNumberOfBytesRecvd. When you call WSARecvFrom, the lpFlags parameter can be 0 for no options, MSG_OOB, MSG_PEEK, or MSG_PARTIAL. These flags can be ORed together. If MSG_PARTIAL is specified when the function is called, the provider knows to return data even if only a partial message has been received. Upon return, the flag MSG_PARTIAL is set if only a partial message was received. Upon return, WSARecvFrom will set the lpFrom parameter (a pointer to a SOCKADDR structure) to the address of the sending machine. Again, lpFromLen points to the size of the SOCKADDR structure, except that in this function it is a pointer to a DWORD. The last two parameters, lpOverlapped and lpCompletionROUTINE, are used for overlapped I/O (which we'll discuss in the next chapter).

Another method of receiving (and sending) data on a connectionless socket is to establish a connection. This might sound strange, but it's not quite what it sounds like. Once a connectionless socket is created, you can call connect or WSAConnect with the SOCKADDR parameter set to the address of the remote machine to communicate with. No actual connection is made, however. The socket address passed into a connect function is associated with the socket so that recv and WSARecv can be used instead of recvfrom or WSARecvFrom because the data's origin is known. The ability to connect a datagram socket is handy if you intend to communicate with only one endpoint at a time in your application.

Sender

To send data on a connectionless socket, there are two options. The first, and simplest, is to create a socket and call either sendto or WSASendTo. We'll cover sendto first, which is defined as

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

The parameters are the same as recvfrom except that buf is the buffer of data to send and len indicates how many bytes to send. Also, the to parameter is a pointer to a SOCKADDR structure with the destination address of the workstation to receive the data. The Winsock 2 function WSASendTo can also be used. This function is defined as

 int WSASendTo( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr FAR * lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE ); 

Again, WSASendTo is similar to its ancestor. This function takes a pointer to one or more WSABUF structures with data to send to the recipient as the lpBuffers parameter, with dwBufferCount indicating how many structures are present. You can send multiple WSABUF structures to enable scatter-gather I/O. Before returning, WSASendTo sets the fourth parameter, lpNumberOfBytesSent, to the number of bytes actually sent to the receiver. The lpTo parameter is a SOCKADDR structure for the given protocol, with the recipient's address. The iToLen parameter is the length of the SOCKADDR structure. The last two parameters, lpOverlapped and lpCompletionROUTINE, are used for overlapped I/O (discussed in Chapter 8).

As with receiving data, a connectionless socket can be connected to an endpoint address and data can be sent with send and WSASend. Once this association is established, you cannot go back to using sendto or WSASendTo with an address other than the address passed to one of the connect functions. If you do attempt to send data to a different address, the call will fail with WSAEISCONN. The only way to disassociate the socket handle from that destination is to call closesocket on the handle and create a new socket.

Message-Based Protocols

Just as most connection-oriented protocols are also streaming, connectionless protocols are almost always message-based. Thus, there are some considerations when you're sending and receiving data. First, because message-based protocols preserve data boundaries, data submitted to a send function blocks until completed. For asynchronous or nonblocking I/O modes, if a send cannot be completely satisfied, the send function returns with the error WSAEWOULDBLOCK. This means that the underlying system was not able to process that data and you should attempt the send call again at a later time. This scenario will be discussed in greater detail in the next chapter. The main thing to remember is that with message-based protocols, the write can occur only as an autonomous action.

On the flip side, a call to a receive function must supply a sufficiently large buffer. If the supplied buffer is not large enough, the receive call fails with the error WSAEMSGSIZE. If this occurs, the buffer is filled to its capacity, but the remaining data is discarded. The truncated data cannot be retrieved. The only exception is for protocols that do support partial messages, such as the AppleTalk PAP protocol. Prior to returning, the WSARecvEx function sets its in-out flag parameter to MSG_PARTIAL when it receives only part of a message.

For datagrams based on protocols supporting partial messages, consider using one of the WSARecv functions. When you make a call to recv, there is no notification that the data read is only a partial message. It is up to the programmer to implement a method for the receiver to determine whether the entire message has been read. Subsequent calls to recv return other pieces of the datagram. Because of this limitation, it can be convenient to use the WSARecvEx function, which allows the setting and reading of the MSG_PARTIAL flag to indicate whether the entire message was read. The Winsock 2 functions WSARecv and WSARecvFrom also support this flag. See the descriptions for WSARecv, WSARecvEx, and WSARecvFrom for additional information about this flag.

Finally, let's take a look at one of the more frequently asked questions concerning sending UDP/IP messages on machines with multiple network interfaces: what happens when a UDP socket is bound explicitly to a local IP interface and datagrams are sent? With UDP sockets, you don't really bind to the network interface; you create an association whereby the IP interface that is bound becomes the source IP address of UDP datagrams sent. The routing table actually determines which physical interface the datagram is transmitted on. If you do not call bind but instead either use sendto/WSASendTo or perform a connect first, the network stack automatically picks the best local IP address based on the routing table. What this means is that if you explicitly bind first, the source IP address could be incorrect. That is, the source IP might not be the IP address of the interface on which the datagram was actually sent.

Releasing Socket Resources

Because there is no connection with connectionless protocols, there is no formal shutdown or graceful closing of the connection. When the sender or the receiver is finished sending or receiving data, it simply calls the closesocket function on the socket handle. This releases any associated resources allocated to the socket.

Putting It All Together

Now that we have covered the necessary steps for sending and receiving data on a connectionless socket, let's look at some actual code that performs this procedure. Figure 7-5 shows the first example, a connectionless receiver. The code illustrates how to receive a datagram on either the default interface or a specified local interface.

Figure 7-5. Connectionless receiver

 // Module Name: Receiver.c // // Description: // This sample receives UDP datagrams by binding to the specified // interface and port number and then blocking on a recvfrom() // call // // Compile: // cl -o Receiver Receiver.c ws2_32.lib // // Command Line Options: // sender [-p:int] [-i:IP][-n:x] [-b:x] // -p:int Local port // -i:IP Local IP address to listen on // -n:x Number of times to send message // -b:x Size of buffer to send // #include <winsock2.h> #include <stdio.h> #include <stdlib.h> #define DEFAULT_PORT 5150 #define DEFAULT_COUNT 25 #define DEFAULT_BUFFER_LENGTH 4096 int iPort = DEFAULT_PORT; // Port to receive on DWORD dwCount = DEFAULT_COUNT, // Number of messages to read dwLength = DEFAULT_BUFFER_LENGTH; // Length of receiving buffer BOOL bInterface = FALSE; // Use an interface other than // default char szInterface[32]; // Interface to read datagrams from // // Function: usage: // // Description: // Print usage information and exit // void usage() { printf("usage: sender [-p:int] [-i:IP][-n:x] [-b:x]\n\n"); printf(" -p:int Local port\n"); printf(" -i:IP Local IP address to listen on\n"); printf(" -n:x Number of times to send message\n"); printf(" -b:x Size of buffer to send\n\n"); ExitProcess(1); } // // Function: ValidateArgs // // Description: // Parse the command line arguments, and set some global flags to // indicate what actions to perform // void ValidateArgs(int argc, char **argv) { int i; for(i = 1; i < argc; i++) { if ((argv[i][0] == '-') || (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'p': // Local port if (strlen(argv[i]) > 3) iPort = atoi(&argv[i][3]); break; case 'n': // Number of times to receive message if (strlen(argv[i]) > 3) dwCount = atol(&argv[i][3]); break; case 'b': // Buffer size if (strlen(argv[i]) > 3) dwLength = atol(&argv[i][3]); break; case 'i': // Interface to receive datagrams on if (strlen(argv[i]) > 3) { bInterface = TRUE; strcpy(szInterface, &argv[i][3]); } break; default: usage(); break; } } } } // // Function: main // // Description: // Main thread of execution. Initialize Winsock, parse the command // line arguments, create a socket, bind it to a local interface // and port, and then read datagrams. // int main(int argc, char **argv) { WSADATA wsd; SOCKET s; char *recvbuf = NULL; int ret, i; DWORD dwSenderSize; SOCKADDR_IN sender, local; // Parse arguments and load Winsock // ValidateArgs(argc, argv); if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { printf("WSAStartup failed!\n"); return 1; } // Create the socket, and bind it to a local interface and port // s = socket(AF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) { printf("socket() failed; %d\n", WSAGetLastError()); return 1; } local.sin_family = AF_INET; local.sin_port = htons((short)iPort); if (bInterface) local.sin_addr.s_addr = inet_addr(szInterface); else local.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (SOCKADDR *)&local, sizeof(local)) == SOCKET_ERROR) { printf("bind() failed: %d\n", WSAGetLastError()); return 1; } // Allocate the receive buffer // recvbuf = GlobalAlloc(GMEM_FIXED, dwLength); if (!recvbuf) { printf("GlobalAlloc() failed: %d\n", GetLastError()); return 1; } // Read the datagrams // for(i = 0; i < dwCount; i++) { dwSenderSize = sizeof(sender); ret = recvfrom(s, recvbuf, dwLength, 0, (SOCKADDR *)&sender, &dwSenderSize); if (ret == SOCKET_ERROR) { printf("recvfrom() failed; %d\n", WSAGetLastError()); break; } else if (ret == 0) break; else { recvbuf[ret] = '\0'; printf("[%s] sent me: '%s'\n", inet_ntoa(sender.sin_addr), recvbuf); } } closesocket(s); GlobalFree(recvbuf); WSACleanup(); return 0; } 

Receiving a datagram is easy. First you create a socket, and then you bind the socket to the local interface. If you bind to the default interface, you can find its IP address by using the getsockname function. This function simply returns the SOCKADDR_IN structure associated with the given socket passed to it, indicating the interface on which the socket is bound. After that, it's just a matter of making calls to recvfrom in order to read the incoming data. Note that we're using recvfrom because we are not concerned with partial messages; the UDP protocol does not support partial messages. In fact, when the TCP/IP stack receives pieces of a larger datagram message, it waits until all parts have been assembled. If the pieces are out of order or one or more pieces are missing, the stack discards the whole message.

Figure 7-6 provides the code for the next example, a connectionless sender. The sender example has quite a few more options than the receiver does. The necessary parameters are the IP address and port of the remote recipient. The -c option specifies whether to make a call to connect first; the default behavior is not to make that call. Again, the steps are simple. First create the socket. If the -c option is present, make a call to connect with the remote recipient's address and port number. This is followed by calls to send. If no connect is performed, simply start sending data to the recipient after socket creation with the sendto function.

Figure 7-6. Connectionless sender

 // Module Name: Sender.c // // Description: // This sample sends UDP datagrams to the specified recipient. // The -c option first calls connect() to associate the // recipient's IP address with the socket handle so that the // send() function can be used as opposed to the sendto() call. // // Compile: // cl -o Sender Sender.c ws2_32.lib // // Command line options: // sender [-p:int] [-r:IP] [-c] [-n:x] [-b:x] [-d:c] // -p:int Remote port // -r:IP Recipient's IP address or host name // -c Connect to remote IP first // -n:x Number of times to send message // -b:x Size of buffer to send // -d:c Character to fill buffer with // #include <winsock2.h> #include <stdio.h> #include <stdlib.h> #define DEFAULT_PORT 5150 #define DEFAULT_COUNT 25 #define DEFAULT_CHAR 'a' #define DEFAULT_BUFFER_LENGTH 64 BOOL bConnect = FALSE; // Connect to recipient first int iPort = DEFAULT_PORT; // Port to send data to char cChar = DEFAULT_CHAR; // Character to fill buffer DWORD dwCount = DEFAULT_COUNT, // Number of messages to send dwLength = DEFAULT_BUFFER_LENGTH; // Length of buffer to send char szRecipient[128]; // Recipient's IP or host name // // Function: usage // // Description: // Print usage information and exit // void usage() { printf("usage: sender [-p:int] [-r:IP] " "[-c] [-n:x] [-b:x] [-d:c]\n\n"); printf(" -p:int Remote port\n"); printf(" -r:IP Recipient's IP address or host name\n"); printf(" -c Connect to remote IP first\n"); printf(" -n:x Number of times to send message\n"); printf(" -b:x Size of buffer to send\n"); printf(" -d:c Character to fill buffer with\n\n"); ExitProcess(1); } // // Function: ValidateArgs // // Description: // Parse the command line arguments, and set some global flags to // indicate what actions to perform // void ValidateArgs(int argc, char **argv) { int i; for(i = 1; i < argc; i++) { if ((argv[i][0] == '-') || (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'p': // Remote port if (strlen(argv[i]) > 3) iPort = atoi(&argv[i][3]); break; case 'r': // Recipient's IP addr if (strlen(argv[i]) > 3) strcpy(szRecipient, &argv[i][3]); break; case 'c': // Connect to recipient's IP addr bConnect = TRUE; break; case 'n': // Number of times to send message if (strlen(argv[i]) > 3) dwCount = atol(&argv[i][3]); break; case 'b': // Buffer size if (strlen(argv[i]) > 3) dwLength = atol(&argv[i][3]); break; case 'd': // Character to fill buffer cChar = argv[i][3]; break; default: usage(); break; } } } } // // Function: main // // Description: // Main thread of execution. Initialize Winsock, parse the command // line arguments, create a socket, connect to the remote IP // address if specified, and then send datagram messages to the // recipient. // int main(int argc, char **argv) { WSADATA wsd; SOCKET s; char *sendbuf = NULL; int ret, i; SOCKADDR_IN recipient; // Parse the command line and load Winsock // ValidateArgs(argc, argv); if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) { printf("WSAStartup failed!\n"); return 1; } // Create the socket // s = socket(AF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) { printf("socket() failed; %d\n", WSAGetLastError()); return 1; } // Resolve the recipient's IP address or host name // recipient.sin_family = AF_INET; recipient.sin_port = htons((short)iPort); if ((recipient.sin_addr.s_addr = inet_addr(szRecipient)) == INADDR_NONE) { struct hostent *host=NULL; host = gethostbyname(szRecipient); if (host) CopyMemory(&recipient.sin_addr, host->h_addr_list[0], host->h_length); else { printf("gethostbyname() failed: %d\n", WSAGetLastError()); WSACleanup(); return 1; } } // Allocate the send buffer // sendbuf = GlobalAlloc(GMEM_FIXED, dwLength); if (!sendbuf) { printf("GlobalAlloc() failed: %d\n", GetLastError()); return 1; } memset(sendbuf, cChar, dwLength); // // If the connect option is set, "connect" to the recipient // and send the data with the send() function // if (bConnect) { if (connect(s, (SOCKADDR *)&recipient, sizeof(recipient)) == SOCKET_ERROR) { printf("connect() failed: %d\n", WSAGetLastError()); GlobalFree(sendbuf); WSACleanup(); return 1; } for(i = 0; i < dwCount; i++) { ret = send(s, sendbuf, dwLength, 0); if (ret == SOCKET_ERROR) { printf("send() failed: %d\n", WSAGetLastError()); break; } else if (ret == 0) break; // Send() succeeded! } } else { // Otherwise, use the sendto() function // for(i = 0; i < dwCount; i++) { ret = sendto(s, sendbuf, dwLength, 0, (SOCKADDR *)&recipient, sizeof(recipient)); if (ret == SOCKET_ERROR) { printf("sendto() failed; %d\n", WSAGetLastError()); break; } else if (ret == 0) break; // sendto() succeeded! } } closesocket(s); GlobalFree(sendbuf); WSACleanup(); return 0; } 



Network Programming for Microsoft Windows
Linux Server Hacks, Volume Two: Tips & Tools for Connecting, Monitoring, and Troubleshooting
ISBN: 735615799
EAN: 2147483647
Year: 1998
Pages: 159

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net