Unix Network Programming Manual
Prev Page Next Page

Multi-client Servers

The bottom of this page displays a program that uses the fork command to implement a multi-client server. This is a relatively "stripped-down" program to not do much, but it is a working example.

The fork command has the following syntax: which is about as simple as you can get. When the fork call is executed, it creates a near duplicate of the program making the call and readies it to run. When the new process is created, it has the same relative program counter as the old process, so it begins by returning from the fork call, just as the original process will do. So now there are two copies of the same program and you might wonder how they can be differentiated. The return value, pid, for the program making the call (the parent) is the process id of the newly created process (the child). For the child the return value is always zero. You might be wondering how this is useful, so a simple example is in order. Most uses of fork look something like this:
After the return from the fork call, the value of pid is checked. Remember that both processes will do exactly the same thing. One will find that the value of pid is greater than zero, and in this case, it will behave like the parent process. The other will see a pid of zero and behave like the child. Normally, the parent behavior is to wait around for another connection by using an accept call. Meanwhile, the child handles the connection with the client and does all of the real work.

One important note is that the code for the child process should not contain a fork call. If it does, the child will create a child, which will create a child and so on, until the operating system process table is full. The machine will crash or have to be shut down and everyone concerned will be quite unhappy.

To better understand what happens here, look at the part of the example that is executed when the pid is zero. While all this does is close the socket and exit, this could be anything. Normally, it is a call to a function that handles all the client activities. The child code in the example below is a call to the function client_server, which performs all ensuing work. Note that it is expected that the child will never return from this call, because the child should never be allowed to execute the code that is meant for the parent. The child process doesn't return, it exits.

The parent code in the example below entails remaining in the do loop, executing wait3, accept and then the fork again. So the parent just continues to wait for more connections while the child is handling the client requests for service.

The wait3 call wait3 call is described in the comments and is required to insure that child processes don't stay around in the system forever. If you don't have one of these, you will hear from your system administrator shortly.

When starting out using fork, the best advice is don't get too adventurous. Use the standard forms and use a few print statements to understand what is going on before trying to do anything fancy. Keep in mind that for beginners:


/* ==================================================================

   This simple example demonstrates a server that processes accepts
   and creates a child process to handle all of the communication
   with the client.  You have to remember that the child
   inherits an environment that is virtually identical to the 
   parent, including all file descriptors.  So the child process
   has access to the sockets created by the parent.

   The function of this server is to allow the client to have
   access to certain operating system functions that might not
   otherwise be available.  This is the sort of thing a distributed
   operating system or database might do, although, hopefully, in a
   more robust and complete way.

   Syntax:
      fork_server port

   ================================================================== */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/wait.h>

#define       NAMELEN       64
#define       CMDLEN        32

typedef int   BOOLEAN;


void client_wait ();
void client_server ();
BOOLEAN legal_name ();


void main 
(
   int argc, 
   char *argv []
)
{
   int                child_id;
   int                status;        /* the returned child status */
   int                l_sock, chct, addrlen;
   char               ch, buf[80];
   char               hostname [NAMELEN];
   struct             hostent *hostp;
   struct sockaddr_in addr;
   

   /*
    * Check the args - client_server port
    */

   if (argc < 2)
   {
      fprintf (stderr, "Syntax: fork_server portnum\n");
      exit (0);
   }

   /* 
    *  Open a stream socket for public access and listen on it
    *  for new clients.  
    */

   if ((l_sock = socket (AF_INET, SOCK_STREAM, 0)) < 0)
   {
      fprintf (stderr, "Could not open the socket\n");
      exit (0);
   }

   bzero (addr, sizeof (addr));
   addr . sin_family = AF_INET;
   addr . sin_port = htons (atoi (argv[1]));
   addr . sin_addr . s_addr = htonl (INADDR_ANY);
   if (bind (l_sock, &addr, sizeof (addr)) < 0)
   {
      fprintf (stderr, "Could not bind the address\n");
      exit (0);
   }

   if ((listen (l_sock, 1)) < 0)
   {
     fprintf (stderr, "Unable to listen\n");
     close (l_sock);
     exit (0);
   }

   /* 
    *  client_wait does the work.
    */

   client_wait (l_sock);

   fprintf (stderr, "cmd_server terminating\n");
   close (l_sock);
}

/* ============================== client_wait ============================

   Function:   Accept client_wait requests and spawn client servers.

   Usage:      client_wait (l_sock)

   Arguments:  int l_sock      - the advertised socket

   Return:     None.

   Notes:

   ================================================================== */

void client_wait (l_sock)
   int l_sock;
{
   int             child_id, client_sock;
   struct sockaddr addr;
   int             addrlen;

   /*
    *  Wait for connections.  If there is a failure, simply go on.
    *  When a connection is made, fork a child process to do the work.
    */

   do 
   {

      /*
       * When a child process dies, it must be recognized by the parent, or
       * the child hangs around as a zombie.  This is handled with a wait.
       * In this case a plain wait () won't work, because we don't want to
       * wait forever.  Instead, use wait3, which clears the child
       * termination signal, but if there isn't anything, will return if
       * the WNOHANG flag is set.
       */

      wait3 (NULL, WNOHANG, NULL);
      if ((client_sock = accept (l_sock, &addr, &addrlen)) < 0)
      {
         fprintf (stderr, "Failed connection with client\n");
         continue;
      }

      /*
       *  Create the child process to do the actual client communications.
       *  Again, the child sees a return of zero and the parent sees the
       *  child id being returned.
       */

      if ((child_id = fork ()) < 0)    
      {
         fprintf (stderr, "Failure in creating child process\n");
         close (client_sock);
      }
      else

         if (child_id == 0)              /* Do the client stuff */    
            client_server (client_sock);


      /*  
       *  The server (parent) has no more use for the client
       *  socket and can close it.  Remember that the child
       *  client server still has it open.
       */

      close (client_sock);

   } while (1);

   /*
    *  The server never terminates, so don't worry about anything here.
    */

   return;
}



/* ============================== client_server ============================

   Function:   Handle client communications.  The task of the server
               is to execute simple commands over the protected
               directory, which would normally be prohibited.

   Usage:      client_server (client_sock)

   Arguments:  int client_sock           - the advertised socket

   Return:     None.

   Notes:

   ================================================================== */

void client_server (client_sock)
   int client_sock;
{
   char buf [CMDLEN];
   int buflen;
   static char *ack = "\06";
   static char *logout_msg = "Pleased to be of service - Goodbye\n";



   /* 
    *  First, make sure that the client address is acceptable. When the
    *  connection is successful, the client should send it's name.
    */

   buflen = recv (client_sock, buf, CMDLEN, 0);
   buf [buflen] = '\0';
   if ( ! legalname (buf))
   {
      fprintf (stderr, "Attempt to access by an illegal client named ");
      buf [buflen] = '\0';
      fprintf (stderr, "%s\n", buf);
      exit (0);
   }

   if (send (client_sock, ack, 1, 0) < 0)
   {
      perror ("On connection acknowledgement");
      close (client_sock);
      exit (0);
   }
   
   /*
    *  Set it up so that standard output for this client server
    *  will be the client socket.  Then whatever output would
    *  normally go to standard output will go back to the client.
    *  dup2 does this by closing stdout (descriptor 1) and making
    *  it a duplicate of the client socket.
    */

   dup2 (client_sock, 1);


   /*
    *  As long as the client is interested, accept communications, 
    *  and execute the desired commands.  At the end of each 
    *  command, return an end-of-command character to the client.  
    *  A command of "logout" indicates a desire to terminate the connection.
    */


   do
   {
      buflen = recv (client_sock, buf, CMDLEN, 0);
      if (strncmp (buf, "logout", 6) == 0)
      {
         /*
          *  Send a nice goodbye and send an ack just in case.
          */

         send (client_sock, logout_msg, strlen (logout_msg), 0);
         send (client_sock, ack, 1, 0);
         break;
      }

      buf [buflen] = '\0';     /* Supply a terminator */
      system (buf);
      send (client_sock, ack, 1, 0);
   } while (1);

   /* 
    *  Terminate the client socket and the process
    */

   close (client_sock);
   exit (0);
}



/* ============================== legal_name =======================

   Function:   Check the legality of a client address/name.

   Usage:      if (legal_name (client))

   Arguments:  char *client      - the client name

   Return:     TRUE if legal, FALSE otherwise.

   Notes:


   ===================================================================== */

BOOLEAN legalname (client_name)
   char *client_name;
{
   char client [NAMELEN], *cp;
   FILE *clientfile;
   BOOLEAN found;

   if ((clientfile = fopen ("client_file", "r")) == NULL)
   {
      perror ("Opening the client file");
      exit (0);
   }
   
   found = FALSE;
   while (! feof (clientfile))
   {
      /* 
       *  Get a line from the file, replace the newline, if any, with a
       *  null and then match it to the client name.
       */

      fgets (client, NAMELEN, clientfile);
      cp = index (client, '\n');
      if (cp != NULL)
         *cp = '\0';
      if (strcmp (client, client_name) != 0)
         continue;

      /* 
       *  Name is located.  Close the file and return.
       */

      fclose (clientfile);
      return (TRUE);
   }

   return (FALSE);        /* No where to be found. */
}


Prev Page Next Page