Home arrow Site Administration arrow Page 3 - Design and Architecture

Forking - Administration

Servers typically need to be able to handle multiple clients simultaneously. This presents several problems that need to be solved. This article addresses three of those issues: allowing multiple clients to connect and stay connnected, efficient use of resources, and keeping the server responsive to each of the clients. It 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).

TABLE OF CONTENTS:
  1. Design and Architecture
  2. Multiplexing
  3. Forking
  4. Preforking: Process Pools
  5. Multithreading
  6. Combining Preforking and Prethreading
  7. Dealing with Large Amounts of Data
By: Apress Publishing
Rating: starstarstarstarstar / 22
November 03, 2005

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement
 

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 Site Administration Articles          >>> More By Apress Publishing
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

SITE ADMINISTRATION ARTICLES

- Coding: Not Just for Developers
- To Support or Not Support IE?
- Administration: Networking OSX and Win 7
- DotNetNuke Gets Social
- Integrating MailChimp with Joomla: Creating ...
- Integrating MailChimp with Joomla: List Mana...
- Integrating MailChimp with Joomla: Building ...
- Integrating MailChimp with Joomla
- More Top WordPress Plugins for Social Media
- Optimizing Security: SSH Public Key Authenti...
- Patches and Rejects in Software Configuratio...
- Configuring a CVS Server
- Managing Code and Teams for Cross-Platform S...
- Software Configuration Management
- Back Up a Joomla Site with Akeeba Backup

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: