Sample Application


Now that we have a basic understanding of Sockets, let s look at a sample application that illustrates some of the functions available in the Sockets API. We ll look at the Sockets API from the perspective of two applications, a client and server that implement the Daytime protocol. This protocol server is ASCII based and simply emits the current date and time when requested by a client. The client connects to the server and emits what is read. This implements the basic flow shown previously in Figure 12.2.

Daytime Server

Let s now look at a C language server that implements the Daytime protocol. Recall that the Daytime server will simply emit the current date and time in ASCII string format through the socket to the client. Upon emitting the data, the socket is closed, and the server awaits a new client connection. Now that we understand the concept behind Daytime protocol server, let s look at the actual implementation (see Listing 12.1).

Listing 12.1: Daytime Server Written in the C Language (on the CD-ROM at ./source/_ch12/dayserv.c )
start example
  1:  #include <sys/socket.h>  2:  #include <arpa/inet.h>  3:  #include <stdio.h>  4:  #include <time.h>  5:  #include <string.h>  6:  #include <unistd.h>  7:   8:  #define MAX_BUFFER        128  9:  #define DAYTIME_SERVER_PORT    13  10:   11:  int main (void)  12:  {  13:  int serverFd, connectionFd;  14:  struct sockaddr_in servaddr;  15:  char timebuffer[MAX_BUFFER+1];  16:  time_t currentTime;  17:   18:  serverFd =  socket  (AF_INET, SOCK_STREAM, 0);  19:   20:  memset(&servaddr, 0, sizeof(servaddr));  21:  servaddr.sin_family = AF_INET;  22:  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  23:  servaddr.sin_port = htons(DAYTIME_SERVER_PORT);  24:   25:   bind  (serverFd,  26:  (struct sockaddr *)&servaddr, sizeof(servaddr));  27:   28:   listen  (serverFd, 5);  29:   30:  while (1) {  31:   32:  connectionFd =  accept  (serverFd,  33:  (struct sockaddr *)NULL, NULL);  34:   35:  if (connectionFd >= 0) {  36:   37:  currentTime = time(NULL);  38:  snprintf(timebuffer, MAX_BUFFER,  39:  "%s\n", ctime(&currentTime));  40:   41:   write  (connectionFd, timebuffer, strlen(timebuffer));  42:   close  (connectionFd);  43:   44:  }  45:   46:  }  47:   48:  } 
end example
 

Lines 1 “6 include the header files for necessary types, symbolic and function APIs. This includes not only the socket interfaces, but also time.h , which provides an interface to retrieve the current time. We specify the maximum size of the buffer that we ll operate upon using the symbolic constant MAX_BUFFER at line 8. The next symbolic constant at line 9, DAYTIME_SERVER_PORT , defines the port number to which we ll attach this socket server. This will allow us to define the well-known port for the Daytime protocol ( 13 ).

We declare our main function at line 11, and then a series of variables are created in lines 13 “16. We create two Socket identifiers (line 13), a socket address structure (line 14), a buffer to hold our string time (line 15), and the GNU/Linux time representation structure (line 16).

The first step in any sockets program is to create our socket using the socket function (line 18). We specify that we re creating an IP socket (using the AF_INET domain) using reliable stream protocol type ( SOCK_STREAM ). The zero as the third argument specifies to use the default protocol of the stream type, which is TCP.

Now that we have our socket, we bind an address and a port to it (lines 20 “26). At line 20, we initialize the address structure by setting all elements to zero. We specify our socket domain again with AF_INET (it s an IPv4 socket). The s_addr element specifies an address, which in this case is the address from which we ll accept incoming socket connections. The special symbol INADDR_ANY says that we ll accept incoming connections from any available interface on the host. We then define the port to use, our prior symbolic constant DAYTIME_SERVER_PORT . The htonl (host-to-network-long) and htons (host-to-network-short) take care of ensuring that the values provided are in the proper byte order for network packets. The final step is using the bind function to bind the address structure previously created with our socket. The socket is now bound with the address, which identifies it in the network stack namespace.

Note  

The Internet operates in big endian, otherwise known as network byte order. Hosts operate in host byte order, which, depending upon architecture, can be either big or little endian. For example, the PowerPC architecture is big endian, and the Intel x86 architecture is little endian. This is a small performance advantage to big endian architectures because they need not perform any byte-swapping to change from host byte order to network byte order (they re already the same).

Before a client can connect to our socket, we must call the listen function (line 28). This tells the protocol stack that we re ready to accept connections (a maximum of five pending connections, per the argument to listen ).

We enter an infinite loop at lines 30 “46 to accept client connections and provide them the current time data. At lines 32 “33, we call the accept function with our socket ( serverFd ) to accept a new client connection. When a client connects to us, the network stack creates a new socket representing our end of the connection and returns this socket from the accept function. With this new client socket ( connectionFd ), we can communicate with the peer client.

At line 35, we check the return socket to see if it s valid (otherwise, an error has occurred and we ignore this client socket). If valid, we grab the current time at lines 37 “39. We use the GNU/Linux time function to get the current time (the number of seconds that have elapsed from January 1, 1970). Passing this value to function ctime converts it into a human-readable format, which is used by sprintf to construct a response string. We send this to the peer client using the connectionFd socket using the write function. We pass our socket descriptor, the string to write ( timebuffer ), and its length. Finally, we close our client socket using the close function, which ends communication with that particular peer.

The loop then continues back to line 32, awaiting a new client connection with the accept function. When a new client connects, the process starts all over again.

From GNU/Linux, we could compile this application using GCC and execute it as follows ( filename server.c ):

 [root@mtjones]$ gcc -o server server.c -Wall     [root@mtjones]$ ./server 
Note  

When executing socket applications that bind to well-known ports (those under 1024), we must start from root. Otherwise, the application will fail with the inability to bind to the reserved port number.

We could now test this server very simply using the Telnet application available in GNU/Linux. As shown, we Telnet to the local host (identified by localhost ) and port 13 (the port we registered previously in the server). The Telnet client connects to our server and then prints out what was sent to it (the time is shown in bold).

 $  telnet localhost 13  Trying 127.0.0.1... Connected to localhost. Escape character is 


GNU/Linux Application Programming
GNU/Linux Application Programming (Programming Series)
ISBN: 1584505680
EAN: 2147483647
Year: 2006
Pages: 203
Authors: M. Tim Jones

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