< Connectionless-UDP, Client & Server | Winsock2 Main | Chap 2: Protocol Characteristics >


 

 

An Intro to Windows Socket Programming with C

Part 7

 

 

What do we have in this chapter 1 part 7?

  1. Running Both the UDP Receiver/Server and UDP Sender/Client

  2. Testing the UDP Client and select Server Programs in Private Network

  3. Message-Based Protocols

  4. Releasing Socket Resources

  5. Miscellaneous APIs

  6. getpeername()

  7. getsockname()

  8. WSADuplicateSocket()

  9. Windows CE

 

Running Both the UDP Receiver/Server and UDP Sender/Client

 

In this test we run both the server and client program on the same host.

1.      Firstly run the previous UDPReceiver program.

The UDP connectionless select Winsock2 and C program: the UDP select server/receiver program in action, waiting client to send datagrams

2.      Then run the UDPSender program. The following screenshot shows both outputs.

The UDP connectionless select Winsock2 and C program: the client and server program in action for the UDP select server with C code

 

Well, you have seen the issue here how the server/receiver can be in the 'listening' mode similar to the TCP counterpart. The recvfrom() part can be put in a loop to achieve this and/or for a better solution, you can use the select() function by setting the timeout and other simple features which provide more controls on the program behaviors. The code snippet is shown below.

 

int recvfromTimeOutUDP(SOCKET socket, long sec, long usec)

{

    // Setup timeval variable

    timeval timeout;

    timeout.tv_sec = sec;

    timeout.tv_usec = usec;

    // Setup fd_set structure

    fd_set fds;

    FD_ZERO(&fds);

    FD_SET(socket, &fds);

    // Return value:

    // -1: error occurred

    // 0: timed out

    // > 0: data ready to be read

    return select(0, &fds, 0, 0, &timeout);

}

 

SelectTiming = recvfromTimeOutUDP(s, 20, 30);

switch (SelectTiming)

{

case 0:

    // Timed out, do whatever you want to handle this situation

    break;

case -1:

    // Error occurred, maybe we should display an error message?

    break;

default:

    // Ok the data is ready, call recvfrom() to get it then

    recvfrom(ReceivingSocket, ReceiveBuf, BufLength, 0, (SOCKADDR *)&SenderAddr, &SenderAddrSize);

}

 

A sample of the working program example that uses the select() function is given below. The project name is UDPServerVer2.

 

#include <winsock2.h>

#include <stdio.h>

 

// A sample of the select() return value

int recvfromTimeOutUDP(SOCKET socket, long sec, long usec)

{

    // Setup timeval variable

    struct timeval timeout;

     struct fd_set fds;

 

    timeout.tv_sec = sec;

    timeout.tv_usec = usec;

    // Setup fd_set structure

    FD_ZERO(&fds);

    FD_SET(socket, &fds);

    // Return value:

    // -1: error occurred

    // 0: timed out

    // > 0: data ready to be read

    return select(0, &fds, 0, 0, &timeout);

}

 

int main(int argc, char **argv)

{

     WSADATA            wsaData;

     SOCKET             ReceivingSocket;

     SOCKADDR_IN        ReceiverAddr;

     int                Port = 5150;

     char          ReceiveBuf[1024];

     int                BufLength = 1024;

     SOCKADDR_IN        SenderAddr;

     int                SenderAddrSize = sizeof(SenderAddr);

     int                ByteReceived = 5, SelectTiming, ErrorCode;

     char ch = 'Y';

 

   // Initialize Winsock version 2.2

   if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0)

   {

        printf("Server: WSAStartup failed with error %ld\n", WSAGetLastError());

        return -1;

   }

   else

          printf("Server: The Winsock DLL status is %s.\n", wsaData.szSystemStatus);

 

     // Create a new socket to receive datagrams on.

     ReceivingSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

 

     if (ReceivingSocket == INVALID_SOCKET)

     {

          printf("Server: Error at socket(): %ld\n", WSAGetLastError());

          // Clean up

          WSACleanup();

          // Exit with error

          return -1;

     }

     else

          printf("Server: socket() is OK!\n");

 

     // Set up a SOCKADDR_IN structure that will tell bind that we

     // want to receive datagrams from all interfaces using port 5150.

 

     // The IPv4 family

     ReceiverAddr.sin_family = AF_INET;

     // Port no. 5150

     ReceiverAddr.sin_port = htons(Port);

     // From all interface (0.0.0.0)

     ReceiverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

 

   // Associate the address information with the socket using bind.

   // At this point you can receive datagrams on your bound socket.

   if (bind(ReceivingSocket, (SOCKADDR *)&ReceiverAddr, sizeof(ReceiverAddr)) == SOCKET_ERROR)

   {

        printf("Server: bind() failed! Error: %ld.\n", WSAGetLastError());

        // Close the socket

        closesocket(ReceivingSocket);

        // Do the clean up

        WSACleanup();

        // and exit with error

        return -1;

     }

     else

          printf("Server: bind() is OK!\n");

 

   // Some info on the receiver side...

   getsockname(ReceivingSocket, (SOCKADDR *)&ReceiverAddr, (int *)sizeof(ReceiverAddr));

 

   printf("Server: Receiving IP(s) used: %s\n", inet_ntoa(ReceiverAddr.sin_addr));

   printf("Server: Receiving port used: %d\n", htons(ReceiverAddr.sin_port));

   printf("Server: I\'m ready to receive a datagram...\n");

 

   SelectTiming = recvfromTimeOutUDP(ReceivingSocket, 10, 0);

 

   switch (SelectTiming)

        {

             case 0:

                 // Timed out, do whatever you want to handle this situation

                 printf("Server: Timeout lor while waiting you bastard client!...\n");

                 break;

             case -1:

                 // Error occurred, maybe we should display an error message?

                // Need more tweaking here and the recvfromTimeOutUDP()...

                 printf("Server: Some error encountered with code number: %ld\n", WSAGetLastError());

                 break;

             default:

                 {

                      while (1)

                      {

                           // Call recvfrom() to get it then display the received data...

                           ByteReceived = recvfrom(ReceivingSocket, ReceiveBuf, BufLength,

                                                    0, (SOCKADDR *)&SenderAddr, &SenderAddrSize);

                           if ( ByteReceived > 0 )

                           {

                               printf("Server: Total Bytes received: %d\n", ByteReceived);

                               printf("Server: The data is \"%s\"\n", ReceiveBuf);

                           }

                           else if ( ByteReceived <= 0 )

                                printf("Server: Connection closed with error code: %ld\n",

                                            WSAGetLastError());

                           else

                                printf("Server: recvfrom() failed with error code: %d\n",

                                        WSAGetLastError());

 

                           // Some info on the sender side

                           getpeername(ReceivingSocket, (SOCKADDR *)&SenderAddr, &SenderAddrSize);

                           printf("Server: Sending IP used: %s\n", inet_ntoa(SenderAddr.sin_addr));

                           printf("Server: Sending port used: %d\n", htons(SenderAddr.sin_port));

                      }

                 }

   }

 

   // When your application is finished receiving datagrams close the socket.

   printf("Server: Finished receiving. Closing the listening socket...\n");

   if (closesocket(ReceivingSocket) != 0)

        printf("Server: closesocket() failed! Error code: %ld\n", WSAGetLastError());

   else

        printf("Server: closesocket() is OK...\n");

 

   // When your application is finished call WSACleanup.

   printf("Server: Cleaning up...\n");

   if(WSACleanup() != 0)

        printf("Server: WSACleanup() failed! Error code: %ld\n", WSAGetLastError());

   else

        printf("Server: WSACleanup() is OK\n");

   // Back to the system

   return 0;

}

 

When we run the UDP receiver/server program, while there is no client connection in 10 seconds, the program terminates.

 

-------------------------------------------------------------

The UDP connectionless select Winsock2 and C program: the UDp receiver/server with select features

 

The following screenshot shows that when there are connections, the socket keeps opening and listening. We test the same client (UDPSender), by sending multiple UDP datagrams. You may want to test this program with the client 'connected' or sending the datagram from different hosts, in private or public network. The following screenshot shows the sender output samples.

 

The UDP connectionless select Winsock2 and C program: the UDP sender/client in actions, sending three datagrams

 

The following screenshot shows the receiver sample output. Keep in mind that both receiver and sender programs were run on the same host.

 

The UDP connectionless select Winsock2 and C program: the UDP select server in action receiving datagrams from clients/sender

 

Testing the UDP Client and select Server Programs in Private Network

 

In this section we try using different hosts in a private network. The receiver IP address is 192.168.1.2 while the sender IP is 192.168.1.1.

1.      Firstly, we run the server program and the following screenshot shows a sample when the sender program was already run and completed the connectionless communication.

The UDP connectionless select Winsock2 and C program: testing the UDP select server in the private network

2.      Unblock the Windows firewall if any.

The UDP connectionless select Winsock2 and C program: unblocking the Windows firewall protection

3.      Then we run the sender/client program. The following screenshot shows a sample output.

The UDP connectionless select Winsock2 and C program: the UDP sender/client in action in the real private network

 

While in the while loop, the client may also provide 'signal' or message to the server for program termination or to make it more useable, you can provide other user control termination such as using the _fgetchar() function and it is left for you as an exercise.

Another improvement that you can implement is providing users with a convenient of supplying the server IP address or domain name and the port number. This simple task can be achieved by manipulating the argc and argv parameters of the main().

 

Message-Based Protocols

 

Just as most connection-oriented communication is also streaming, connectionless communication is 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 non-blocking 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. The main point to remember is that with message-based protocols, the write can occur as an autonomous action only.

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 WSARecv(), WSARecvEx(), or WSARecvFrom() functions set the 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 because when you make a call to recv()/recvfrom(), 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 if the entire message has been read. Subsequent calls to recv()/recvfrom() 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 if 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 about 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 use either sendto()/WSASendTo() or perform a connect first, the network stack automatically picks the best local IP address based on the routing table. So 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.

 

Miscellaneous APIs

 

In this section, we'll cover a few Winsock API functions that you might find useful when you put together your own network applications.

 

getpeername()

 

This function is used to obtain the peer's socket address information on a connected socket. The function is defined as:

 

int getpeername(

    SOCKET s,

    struct sockaddr FAR* name,

    int FAR* namelen

);

 

The first parameter is the socket for the connection; the last two parameters are a pointer to a SOCKADDR structure of the underlying protocol type and its length. For datagram sockets, this function returns the address passed to a connect() call; however, it will not return the address passed to a sendto() or WSASendTo() call.

 

getsockname()

 

This function is the opposite of getpeername(). It returns the address information for the local interface of a given socket. The function is defined as follows:

 

int getsockname(

    SOCKET s,

    struct sockaddr FAR* name,

    int FAR* namelen

);

 

The parameters are the same as the getpeername parameters except that the address information returned for socket s is the local address information. In the case of TCP, the address is the same as the server socket listening on a specific port and IP interface.

 

WSADuplicateSocket()

 

The WSADuplicateSocket() function is used to create a WSAPROTOCOL_INFO structure that can be passed to another process, thus enabling the other process to open a handle to the same underlying socket so that it too can perform operations on that resource. Note that this is necessary only between processes; threads in the same process can freely pass the socket descriptors. This function is defined as:

 

int WSADuplicateSocket(

    SOCKET s,

    DWORD dwProcessId,

    LPWSAPROTOCOL_INFO lpProtocolInfo

);

 

The first parameter is the socket handle to duplicate. The second parameter, dwProcessId, is the process ID of the process that intends to use the duplicated socket. Third, the lpProtocolInfo parameter is a pointer to a WSAPROTOCOL_INFO structure that will contain the necessary information for the target process to open a duplicate handle. Some form of interprocess communication must occur so that the current process can pass the WSAPROTOCOL_INFO structure to the target process, which then uses this structure to create a handle to the socket (using the WSASocket() function).

Both socket descriptors can be used independently for I/O. Winsock provides no access control, however, so it is up to the programmer to enforce some kind of synchronization. All of the state information associated with a socket is held in common across all the descriptors because the socket descriptors are duplicated, not the actual socket. For example, any socket option set by the setsockopt() function on one of the descriptors is subsequently visible using the getsockopt() function from any or all descriptors. If a process calls closesocket() on a duplicated socket, it causes the descriptor in that process to become deallocated. The underlying socket, however, will remain open until closesocket is called on the last remaining descriptor.

In addition, be aware of some issues with notification on shared sockets when using WSAAsyncSelect() and WSAEventSelect(). Issuing either of these calls using any of the shared descriptors cancels any previous event registration for the socket regardless of which descriptor was used to make that registration. Thus, for example, a shared socket cannot deliver FD_READ events to process A and FD_WRITE events to process B. If you require event notifications on both descriptors, you should rethink your application design so that it uses threads instead of processes.

 

Windows CE

 

All the information in the preceding sections applies equally to Windows CE. The only exception is that because early Windows CE versions are based on the Winsock 1.1 specification, none of the Winsock 2–specific functions, such as WSA variants of the sending, receiving, connecting, and accepting functions, is available. The only WSA functions available with Windows CE are WSAStartup(), WSACleanup(), WSAGetLastError(), WSASetLastError(), and WSAIoctl(). We have already discussed the first three of these functions; the last will be covered in other chapter.

Windows CE supports the TCP/IP protocol, which means you have access to both TCP and UDP. In addition to TCP/IP, infrared sockets are also supported. The IrDA protocol supports only stream-oriented communication. For both protocols, you make all the usual Winsock 1.1 API calls for creating and transmitting data. The only exception has to do with a bug in UDP datagram sockets in Windows CE 2.0: every call to send() or sendto() causes a kernel memory leak. This bug was fixed in Windows CE 2.1, but because the kernel is distributed in ROM, no software updates can be distributed to fix the problem with Windows CE 2.0. The only solution is to avoid using datagrams in Windows CE 2.0.

Take note that Windows CE does not support console applications and uses UNICODE only. The purpose of our examples is to teach the core concepts of Winsock without having to trudge through code that doesn't relate to Winsock. Unless you're writing a service for Windows CE, a user interface is almost always required. This entails writing many additional functions for window handlers and other user-interface elements, which can obfuscate what we're trying to teach. In addition, there is the dilemma of UNICODE vs. non-UNICODE Winsock functions. It is up to the programmer to decide if the strings passed to the sending and receiving Winsock functions are UNICODE or ANSI strings. Winsock doesn't care what you pass as long as it's a valid buffer. (Of course, you might need to typecast the buffer to silence the compiler warnings.) Don't forget that if you cast a UNICODE string to char*, the length parameter for how many bytes to send should be adjusted accordingly. In Windows CE, if you want to display any data sent or received, you must take into account whether it is UNICODE so that it can be displayed, as all the other Windows system functions do require UNICODE strings. In sum, Windows CE requires a great deal more housekeeping to make a simple Winsock application.

If you do want to run these examples on Windows CE, only a few minor modifications are required for the Winsock code to compile. First, the header file must be WINSOCK.H, as opposed to WINSOCK2.H. WSAStartup() should load version 1.1 because that is the current version of Winsock in Windows CE. Also, Windows CE does not support console applications so you must use WinMain() instead of main(). Note that this does not mean you are required to incorporate a window into your application; it just means you can't use console text I/O functions such as printf().

The Windows CE .NET 4.1 and later implement the Winsock 2.2 and are fully backward compatible with the Winsock 1.1. The Header is Winsock2.h and the Library is Ws2.lib.

 

 

 


< Connectionless-UDP, Client & Server | Winsock2 Main | Chap 2: Protocol Characteristics >