In this chapter, we looked at nine different server designs and ran them all against the same Web-style client, comparing the amount of CPU time spent performing process control:
0. Iterative server (baseline measurement; no process control)
1. Concurrent server, one fork per client
2. Preforked, with each child calling accept
3. Preforked, with file locking to protect accept
4. Preforked, with thread mutex locking to protect accept
5. Preforked, with parent passing socket descriptor to child
6. Concurrent server, create one thread per client request
7. Prethreaded with mutex locking to protect accept
8. Prethreaded with main thread calling accept
We can make a few summary comments:
First, if the server is not heavily used, the traditional concurrent server model, with one fork per client, is fine. This can even be combined with inetd, letting it handle the accepting of each connection. The remainder of our comments are meant for heavily used servers, such as Web servers.
Creating a pool of children or a pool of threads reduces the process control CPU time compared to the traditional one-fork-per-client design by a factor of 10 or more. The coding is not complicated, but what is required, above and beyond the examples that we have shown, is monitoring the number of free children and increasing or decreasing this number as the number of clients being served changes dynamically.
Some implementations allow multiple children or threads to block in a call to accept, while on other implementations, we must place some type of lock around the call to accept. Either file locking or Pthread mutex locking can be used.
Having all the children or threads call accept is normally simpler and faster than having the main thread call accept and then pass the descriptor to the child or thread.
Having all the children or threads block in a call to accept is preferable over blocking in a call to select because of the potential for select collisions.
Using threads is normally faster than using processes. But, the choice of one-child-per-client or one-thread-per-client depends on what the OS provides and can also depend on what other programs, if any, are invoked to service each client. For example, if the server that accepts the client's connection calls fork and exec, it can be faster to fork a single threaded process than to fork a multithreaded process.