Site Administration Page 4 - Design and Architecture |
While the preceding strategy is simple to implement, there is a performance penalty to be paid. Creating a copy of a running process is expensive (in terms of time as well as resources), especially for large applications. As clients start connecting in large numbers, there can be a noticeable delay in launching the child process. One strategy to mitigate the startup costs for a process is to fork a number of processes into a “process pool” when the application starts. This is called pre-forking, and it restricts all of the costs associated with creating a child process to the initialization section of the application. When a client connects, the process to handle it has already been created. Using this method,accept()is not called in the parent process, but in each child process. Unlike the previous example, the listening socket descriptor will not be closed in the child process. In fact, all of the children will be callingaccept()on the same listening socket. When a client connects, the kernel chooses one of the children to handle the connection. Since the child is already running, there is no process creation delay. Example: Apache Web Server The original Apache Web Server (prior to version 2),http://httpd.apache.org, uses process pools. However, it takes them one step further by making the process pool size dynamic. In the Apache configuration file, you are able to specify the number of initial children, the maximum number of children, the minimum number of idle children, and the maximum number of idle children. The initial and maximum number of children is pretty straightforward. Specifying the minimum and maximum number of idle children allows the server to handle sudden spikes in usage. The parent process continually checks on the child processes to see how many are idle. It then terminates extra children or creates new children depending on the settings. Using configuration settings, the server can be finely tuned for maximum performance. Apache version 2 takes this even a step further by introducing thread pools. Thread pools are similar to process pools in that you generate the handlers to deal with connecting clients during the initialization of the application, but you are creating threads instead of processes. We’ll talk about thread pools in the section “Prethreading: Thread Pools.” A Preforking Server In the following program (server3.c), the parent server process uses a loop to create the specified number of child processes. On execution, we can pass in the number of children to fork, on the command line. The parent server process then callswait()to keep it from returning before any of its children. If we don’t insert this call, the parent process will end immediately. Each child then calls accept on the same listening socket and waits for a client connection. When a connection is made, the operating system chooses one of the children to signal using a “first in, first out” methodology. That child receives the data from the client and echoes it back. Finally, the connection is closed, and the child callsaccept()again to wait for another client. Figure 5-5 shows the basic architecture for process pools.
The initial section is again similar to the earlier programs, except that we check the command line to see how large a process pool to create: /* server3.c */ First, we declare the variables that we will need. struct sockaddr_in sAddr; Then, we check the command line to see how many processes will be in our process pool. If nothing is specified, then we create only one listening process. if (argc > 1) { We create the socket that will listen for incoming connections. listensock = socket(AF_INET, SOCK_STREAM, Again, we set theSO_REUSEADDRoption. val = 1; Next, we bind it to a local port and all addresses associated with the machine. sAddr.sin_family = AF_INET; Now we put it into listening mode. result = listen(listensock, 5); We create the specified number of child processes for the process pool using thefork()system call: for (x = 0; x < nchildren; x++) { Each child process calls accept on the same listening socket. When a client connects, the system will choose the next child in line to notify: while (1) { Once a client connects, we read characters it sends, echo them to the screen and client, and close the connection: printf("client connected to child This tells the parent process to wait until all of the children have been completed, before continuing. Of course, none of the children in this example will ever be completed: wait(NULL); Figure 5-6 shows a sample of the output obtained on executing the program. The client was run with five child processes.
Notice that the processes are used in the order in which they calledaccept(). Since we have more clients than processes in the process pool, earlier processes are reused for new clients once they become free.
blog comments powered by Disqus |
|
|
|
|
|
|
|