SunQuest
 
       Administration
  Home arrow Administration arrow Page 3 - Design and Architecture
Dev Shed Forums 
Administration  
AJAX  
Apache  
BrainDump  
DHTML  
Flash  
Java  
JavaScript  
Multimedia  
MySQL  
Oracle  
Perl  
PHP  
Practices  
Python  
Reviews  
Security  
Style-Sheets  
Web Services  
XML  
Zend  
Zope  
Forums Sitemap 
IBM® developerWorks 
Sun Developer Network 
Dedicated Servers 
E-Commerce Hosting 
Linux Web Hosting 
Managed Hosting 
Small Business Hosting 
Actuate Whitepapers 
VeriSign Whitepapers 
VPS Hosting 
Weekly Newsletter

 
Developer Updates  
Free Website Content 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us Get Paid 
Request Media Kit
Contact Us 
Site Map 
Privacy Policy 
Support 
 USERNAME
 
 PASSWORD
 
 
  >>> SIGN UP!  
  Lost Password? 
ADMINISTRATION

Design and Architecture
By: Apress Publishing
  • Search For More Articles!
  • Disclaimer
  • Author Terms
  • Rating: 5 stars5 stars5 stars5 stars5 stars / 19
    2005-11-03

    Table of Contents:
  • Design and Architecture
  • Multiplexing
  • Forking
  • Preforking: Process Pools
  • Multithreading
  • Combining Preforking and Prethreading
  • Dealing with Large Amounts of Data

  • Rate this Article: Poor Best 
      ADD THIS ARTICLE TO:
      Del.ici.ous Digg
      Blink Simpy
      Google Spurl
      Y! MyWeb Furl
    Email Me Similar Content When Posted
    Add Developer Shed Article Feed To Your Site
    Email Article To Friend
    Print Version Of Article
    PDF Version Of Article
     
     
    ADVERTISEMENT

    Stay one step ahead of the competition. Evaluate and give feedback on some of the hottest web development tools on the market today. Make your opinion heard! Click Here

    Design and Architecture - Forking


    (Page 3 of 7 )

     

    In the UNIX environment, the traditional way to handle multiple clients is to use the fork() system call. When an application callsfork(), an exact duplicate of the calling program is made, right down to the program counter (PC), and a new child process is started with that copy. Everything (except the parent’s process ID, or PID) is copied. This includes the parent’s heap, stack, data space, and all open descriptors. Then, the system call returns twice: once in the calling program and the next time in the child process. The return value in the calling program is the PID of the new child process, while in the child process it is 0.

    This can be a little confusing at first. How can a function that is called once return twice, you ask? If you think carefully about what thefork()call does, though, it is very logical. Callingfork()makes an exact copy of the program. This means that when the copy begins execution, it starts at the exact place the calling program was, which is thefork()call.

    Let’s see, in a little more detail, the consequences of copying descriptors. As mentioned previously, when the child process is created, everything is copied to the child, including all open descriptors. The Linux kernel keeps a reference count for each descriptor. So, when a child is created, the reference count is incremented for each copy. As a result, the client must close the descriptor of the listening socket used by the parent process, and the parent must close the descriptor of the client socket used by the child process. This will become evident on executing the programserver2.c. Ifclose()is not called on these sockets, the reference count in the kernel will be wrong, resulting in open or stale connections potentially abusing or exhausting system resources as time goes on.

    Using thefork()system call to handle multiple clients has several advantages. First, it’s simple. Creating a new process to handle each client is easy to implement. Second, using a process per client keeps any one client from monopolizing the server, because the Linux kernel will preemptively swap the processes in and out. Third, other child processes won’t be affected if one of the child processes crashes, because the kernel prevents one process from damaging memory in another process.

    Thefork()system call isn’t without its disadvantages, however. The most notable problem with the multiprocess approach is the lack of shared memory. Over the years, shared memory solutions (likeshmget()) have been made available for multiprocess applications, but it isn’t as elegant as with a threaded approach.shmget()is a system call that allows the allocation of a shared memory segment that can be accessed by multiple processes. The way it works is that the parent process creates a shared memory segment upon startup. Then, as each child is created, it inherits the attachment to the shared memory. Even with the shared memory, access to it must be synchronized with semaphores. Finally, with large programs, significant resources can be used because everything must be copied for each child, resulting in slow performance and potential exhaustion of resources.


    CAUTION 
    When using thefork()system call, you must be very careful to not create zombies. Zombies are child processes that occur when the parent process exits without callingwait()orwaitpid()on the child process. The kernel keeps the exit information for these child processes until the parent process callswait()orwaitpid()to retrieve it. If the parent exits without retrieving the exit information, the child processes remain in a zombie state. Eventually the kernel will clean them up, but it is best to avoid them in the first place to free up system resources. The simplest way to handle this issue is by trapping theSIGCHLDsignal and callingwaitpid(). This is demonstrated in the forking server in the next section.

    One Process Per Client

    The simplest architecture for a multiprocess server is to use one process per client. The server simply waits for a client to connect and then creates a process to handle it. From an application design standpoint, this is much less cumbersome than the multiplexing approach we examined earlier. Each client has a dedicated process, and the client logic flows linearly without worrying about stopping to service other connected clients, as compared to multiplexing, where a single process must deal with all clients simultaneously.

    A Forking Server

    In the following program (server2.c), the initial process waits for a client to connect. It then callsfork()to create a new child process to handle the client. Next, the child process reads the data from the client and echoes it back. Finally, the connection is closed, and the child exits. Meanwhile, the parent process loops back to listen for another connection. Figure 5-3 shows the basic architecture for a multiprocess server.


    Figure 5-3.  Basic architecture for a multiprocess server

    The initial section of the code is similar to the earlier program,server1.c:

    /* server2.c */
    #include <stdio.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>

    To trap the child exits and prevent zombies, we also need the following two header files:

    #include <sys/wait.h>
    #include <signal.h>

    Here’s our signal handler. It simply callswaitpid()for any exited children. The reason we call it in a loop is that there may not be a one-to-one correlation between exited children and calls to our signal handler. POSIX does not allow for the queuing of signal calls, so our handler may be called once when several children have exited and we need to callwaitpid()for each one.

    void sigchld_handler(int signo)
    {
      while (waitpid(-1, NULL, WNOHANG) > 0);
    }


    Next, we declare the variables that we will need.

    int main(int argc, char *argv[])
    {
      struct sockaddr_in sAddr;
      int listensock;
      int newsock;
      char buffer[25];
      int result;
      int nread;
      int pid;
      int val;

    Then we create the socket that will accept the incoming connections.

      listensock = socket(AF_INET, SOCK_STREAM,
      IPPROTO_TCP);

    Here we set ourSO_REUSEADDRoption.

      val = 1;
      result = setsockopt(listensock, 
    SOL_SOCKET, SO_REUSEADDR, &val, sizeof
    (val));
      if (result < 0) {
             
    perror(“server2”);
              return 0;
      }

    We then bind it to a local port and all addresses associated with the machine.

      sAddr.sin_family = AF_INET;
      sAddr.sin_port = htons(1972);
      sAddr.sin_addr.s_addr = INADDR_ANY;
      result = bind(listensock, (struct sockaddr
    *) &sAddr, sizeof(sAddr));
      if (result < 0) {
       
    perror("server2");
        return 0;
      }

    Afterward, we put the socket into listening mode to listen for incoming connections.

      result = listen(listensock, 5);
     
    if (result < 0) {
        perror("server2");
        return 0;
     
    }

    Before we start looping, we install our signal handler.

    signal(SIGCHLD, sigchld_handler);

    We then callaccept()and allow it to block waiting for connection requests from clients. After accept returns, we callfork()to create a new process. If it returns 0, then we are in the child process; otherwise, the PID of the new child is returned.

      while (1) {
        newsock = accept(listensock, NULL, 
    NULL);
        if ((pid = fork()) = = 0) {

    Once in the child process, we close the listening socket. Remember that all descriptors are copied from the parent process to the child. The child process does not need the listening socket any longer, so we close the child’s reference on that socket. However, the socket will remain open in the parent process. Next, we read characters from the client and echo them to the screen. Finally, we send the characters back to the client, close the socket, and exit the child process:

      printf("child process %i created.\n", 
    getpid());
      close(listensock);
      nread = recv(newsock, buffer, 25, 0); 
      buffer[nread] = '\0';
      printf("%s\n", buffer);
      send(newsock, buffer, nread, 0);
      close(newsock);
      printf("child process %i finished.\n", 
    getpid());
      exit(0);
    }

    This line is only reached in the parent process. Since the child process has a copy of the client socket, the parent process closes its reference here to decrease the kernel reference count. The socket will remain open in the child process:

        close(newsock);
      }
    }

    The server can be compiled with a command similar to the example client. Figure 5-4 shows sample output obtained on executing the preceding program. The client was run with five child processes.


    Figure 5-4.  Output from a multiprocess server

    More Administration Articles
    More By Apress Publishing


       · This article is an excerpt from the book "Definitive Guide to Linux Network...
     

    Buy this book now. This book is excerpted from chapter five of the book The Definitive Guide to Linux Networking Programming, written by Keir Davis et. al. (Apress, 2004; ISBN: 1590593227). Check it out today at your favorite bookstore. Buy this book now.

       

    ADMINISTRATION ARTICLES

    - Network Administration with FreeBSD 7
    - Components of an Information Architecture
    - The Anatomy of an Information Architecture
    - Configuring Load-Balanced Clusters
    - Load-Balanced Clusters
    - UNIX Time Format Demystified
    - Making Changes in the CVS
    - Building Your First CVS Repository
    - CVS Quickstart Guide
    - Authorizing Users in Samba
    - Handling User Accounts in Samba
    - Authentication in Samba
    - Accounts, Authentication, and Authorization
    - Advanced Concepts on Dealing with Files and ...
    - Dealing with Files and Filesystems





    © 2003-2008 by Developer Shed. All rights reserved. DS Cluster 1 hosted by Hostway