Stream (TCP) ServerClient

 < Day Day Up > 



Stream (TCP) Server/Client

The stream server and client demonstrate the Daytime protocol using stream sockets (reliable TCP-based communication).

Stream Server

The Daytime protocol stream server is shown in Listing 16.1.

Listing 16.1 C language Daytime stream server.

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

The first item to note about Listing 16.1 is the header files shown in lines 1 and 2. These make visible a host of function prototypes, types, and symbolic constants needed for the stream server. The file socket.h (under subdirectory sys) defines the Sockets API (in this case, socket, bind, listen, and accept). Header file inet.h provides the INADDR_ANY symbolic, htonl/htons macros, and the sockaddr_in type. The remaining include files (lines 3–5) make available other system resources such as the time, printf, and write library functions.

Next, we define two symbolic constants in lines 7 and 8. These symbolic constants are nothing more than symbols used by the C preprocessor for replacement in the source, and they provide meaningful names in the source rather than ambiguous number constants.

Our sample stream server program begins at line 10 with the definition of the main program (all C applications have a main, which represents the entry point for the program). Two socket identifiers are declared in line 12 and our address structure (to represent the server) is declared in line 13. We also declare a character buffer (used to send data to the client) in line 14 and a time variable used to extract the current system time in line 15.

We start our server, at line 17, with the creation of a new socket to represent our server. The identifier for the sock is returned by the socket call. Note that we’re specifying three arguments with socket to create a stream socket (SOCK_STREAM) for Internet communication (AF_INET). The third argument to socket is defined as 0, and represents a protocol within the family SOCK_STREAM. Because TCP is only recognized here for SOCK_STREAM, no further definition is necessary.

As we’re building a server, a name must be bound to the socket so that clients can address it. This naming process occurs in lines 19–22. After clearing out the address structure using memset, the protocol family is defined as AF_INET (permitting Internet communication). Next, the address is defined as INADDR_ANY, which means that the server will accept connections on any available interface. Finally, the port for the server is defined as the DAYTIME_SERVER_PORT (or 13). Note the use of htonl/htons in lines 21 and 22. The network stack requires that these two elements be in network byte order; therefore, the host-to-network-long/short functions are used to convert these from host to network byte ordering.

In lines 24 and 25, we bind the servaddr name created in lines 19–22 to the socket. This name represents the local name for the socket. After bind is performed on an address/port pair, this local name is active and may not be rebound to another socket.

Next, in line 27, we permit incoming connections with the server socket using the listen call. In this call, we identify the server socket and a backlog. The backlog defines the number of connections that may be queued for the particular server socket. After listen is called, incoming connections are permitted for the stream socket.

We begin an infinite loop at line 29, where the server accepts and handles incoming client connections. The call to accept, lines 31–32, blocks until an incoming client connection is accepted. The new client connection, returned from accept, is stored into connectionFd. Note that we pass two NULL values to accept, which could have been actual pointer references to store the host/port information of the connected client peer. Because we’re not interested, we pass NULLs to ignore this information.

Upon verifying that a valid socket was returned from accept in line 34, we construct a string version of the current system time using the time system function and sprintf to build the string (lines 36–38). The write function is then used to send the time string (stored in timebuffer) to the client. Note that the connectionFd socket descriptor is used to send the data (as returned by accept), referencing both the timebuffer and its length.

Finally, once the data is written through the socket to the client, the newly created socket (represented by connectionFd) is closed using the close function (line 41). The server socket (serverFd) is not closed, as we’ll loop back to the accept function at line 31 to await a new client connection.

Stream Client

Now, let’s look at the Daytime stream client (shown in Listing 16.2). The client creates a socket to connect to the server and then awaits a string to be received through the socket. Upon receiving the string, it’s printed and the client exits.

Listing 16.2 C language Daytime stream client.

start example
 1   #include <sys/socket.h>  2   #include <arpa/inet.h>  3   #include <stdio.h>  4   #include <unistd.h>  5   #include <time.h>  6  7   #define MAX_BUFFER            128  8   #define DAYTIME_SERVER_PORT   13  9 10   int main ( ) 11   { 12     int connectionFd, in; 13     struct sockaddr_in servaddr; 14     char timebuffer[MAX_BUFFER+1]; 15 16     connectionFd = socket(AF_INET, SOCK_STREAM, 0); 17 18     memset(&servaddr, 0, sizeof(servaddr)); 19     servaddr.sin_family = AF_INET; 20     servaddr.sin_port = htons(DAYTIME_SERVER_PORT); 21 22     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 23 24     connect(connectionFd, (struct sockaddr_in *)&servaddr, 25              sizeof(servaddr)); 26 27     while ( (in = read(connectionFd, 28                         timebuffer, MAX_BUFFER)) > 0) { 29       timebuffer[in] = 0; 30       printf("\n%s", timebuffer); 31     } 32 33    close(connectionFd); 34 35     return(0); 36   }
end example

Lines 1–14 are identical to the server; refer to the discussion of Listing 16.1 for more information about this code segment.

In line 16, we create a socket, identically to the socket created by the server (a stream socket). We also create an address structure (in lines 18–22), but in this case, we’re not creating the address structure to name the server. In this case, we’re creating the address structure to define where the client should connect. Note in line 22, that we specify an actual address instead of INADDR_ANY. For the server, the symbolic INADDR_ANY specifies that the server can accept a connection through any interface. In the case of the client, we’re specifying an IP address to which we’ll connect. This IP address is a special address that represents the loopback address (127.0.0.1). This means that our connection will be “looped-back” onto the same host (because the client and server, in this example, occupy the same host). This could have been an actual IP address, representing another host on the Internet to which to connect.

We use the connect function, at line 24, to create a connection between the client and server sockets. Note that we use our previously created socket (connectionFd) and the address structure, servaddr, which represents our intended target server.

With our connection created, we go into a loop to read the time string sent by the server (line 27 and 28). Any data received is printed out for view (line 30). The call to the read function will block, but when the server closes the socket (upon sending the time string), the read call will return with an error. This error code will be zero, which allows us to exit out of the loop. Note that this error won’t be returned until we’ve read any data that’s available. In the read call, we reference the client socket, the timebuffer to store the received string, and the maximum number of bytes to accept in this call.

After we’ve detected that the server has closed its end of the socket, we close our side at line 33. The client is then exited at line 35 and the process is complete.



 < Day Day Up > 



BSD Sockets Programming from a Multi-Language Perspective
Network Programming for Microsoft Windows , Second Edition (Microsoft Programming Series)
ISBN: 1584502681
EAN: 2147483647
Year: 2003
Pages: 225
Authors: Jim Ohlund

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