A server is a program that runs forever, waiting for connections from clients. When a client connects to a server, the server performs some function for the client. If the server is an iterative server, it may only process one client at a time. If it is a concurrent server, then it can process multiple clients.
To make a distinction between client and server, the following is usually true:
Client: being lightweight, supporting only a single user, able to run on small machines, designed to be switched on when needed and off when the user is done, lacking preemptive multitasking, optimized for low latency, and putting a lot of its resources into fancy user interfaces. Server: being heavyweight, capable of running continuously, optimized for throughput, fully pre-emptive multitasking to handle multiple sessions.
Examples of servers are the rloginprogram (which lets clients log into a machine remotely), ftp, finger, etc. Most of the time, the client runs telnet, which just sends stdin of the client to the server, and output from the server to stdout of the client. More complex programs require the client to perform sophisticated work. For example, netscape has a graphical user interface that the client runs, and this interface interprets output from the server, and displays it in a nice way.
An example of a simple server is catmotd.c -- note how it lets anyone access the file motd by telneting to the machine/port that is being served by the program. I am currently running a catmotd server on plank, port 7001. If you type "telnet plank 7001", you'll see the contents of the motd file, which you cannot access otherwise because it is protected 0600.
Now, suppose you've written a brilliant educational tool like /usr/games/arithmetic. You want to let anyone in the world play it by calling telnet to your machine. Then you could write a program like iterative_serve1.c. What this program does is serve a socket, and wait for connections. For each connection, it executes the command
/blugreen/homes/bin/SUN4/plank/pty -ne /usr/games/arithmetic
Moreover stdin and stdout are dup'd to be the socket. Why use pty? Well, it's a program that forces its argument program to treat stdin and stdout like they are going to the screen, regardless of what stdin and stdout really do. This is nice, because it means that the standard io buffers will be flushed after every newline, and not after every 4K characters. This is necessary for the server to function correctly. If you want more information on pty, read chapter 19 of the book. Feel free to copy pty to your own bin directory. You don't need to know anything else about pty, but it is a very useful program.
Note how iterative_serve1.c saves stdin and stdout and restores them once the program is over.
Try the program out. Copy iterative_serve1.c compile it and run it (on a SunOS machine like duncan). As arguments, use your machine name, and some number > 4999. Now, run a client program using telnet machine port. You should be able to take the arithmetic quiz now. You can do this from a different machine, or even a different user account. Try it out. When your client is done, type CNTL-], and then "quit". Note that only one client can attach at a time. This is a drag, especially with an interactive program like this. To solve this problem, instead of calling doit(), the server can call fork() and doit() as in concurrent_serve1.c. Note that by calling fork(), you don't need to save and restore stdin and stdout. Moreover, you can call execlp instead of system. This saves you one process forking, since system() calls fork.
Try running concurrent_serve1, and attaching a few clients to it simultaneously. Now, have the clients quit, but keep the server running. Now, do a "ps x" on the server's machine. You'll see that there are a couple of zombie processes (the ones that say "defunct"). Why is that? Because these processes have died, but their parent process (the server) has not called wait(). This is a drag if you want to write a server that lives forever, and processes client requests. If you write this server like concurrent_serve1, then you'll get a blowup of zombie processes over time.
How to fix this? Well, there are two ways -- one is to try to call wait for processes that have finished. However, since you don't know when they're going to finish, you'll have to call a non-blocking version of wait() like wait3 (read the man page).
The other way is to force the process to be an orphan, so that it will simply go away when it's done. How to do this? Look in concurrent_serve2.c -- you fork off a process that forks off the /usr/games/arithmetic process. The first process then exits (and you call wait() for that process), which makes the /usr/games/arithmetic an orphan. Now, when it's done, it goes away.