So, initialization consists of the following steps:
main(int argc, char **argv) { int sock, connection, dummy, nclients, maxcli; int p[2], *pipes, i, mx; fd_set fds; maxcli = atoi(argv[1]); sock = serve_socket("plank", 5005); nclients = 0; pipes = (int *) malloc(sizeof(int)*maxcli); for (i = 0; i < maxcli; i++) pipes[i] = -1;The main loop of the program is like before. There are two parts -- what to do if there are less than maxcli clients, and what to do if there are exactly maxcli clients. In the former case, accept_connection() is called as before, but then something different is done with the connection. First pipe() is called to open a new pipe, and then a child is forked. That child closes all file descriptors except connection and the write end of the pipe. This includes all of the open file descriptors in pipes, which are duplicated over the fork() call. Now, that child forks a grandchild and exits. The grandchild calls doserver, and services the client. The original csm process closes the connection and the write end of the pipe, waits for the child to exit, and then finds an empty element of pipes into which it copies the read end of the pipe. This part of the code looks as follows:
while(1) { if (nclients < maxcli) { connection = accept_connection(sock); nclients++; pipe(p); if (fork() == 0) { /* Close all fds except the connection and p[1] */ close(sock); close(p[0]); for (i = 0; i < maxcli; i++) if (pipes[i] != -1) close(pipes[i]); /* fork the grandchild and exit */ if (fork() == 0) { doserver(connection); } else { exit(0); } } else { close(connection); close(p[1]); wait(&dummy); for (i = 0; i < maxcli && pipes[i] != -1; i++) ; pipes[i] = p[0]; }When the number of clients reaches maxcli, the csm process must wait for some grandchildren to finish. It does this by calling select() on all the read ends of the pipes. Thus, it initializes a fd_set to contain all the read ends of the pipes, and calls select(). If a grandchild is done, select() will report that its pipe has something to read (of course, the read() call would return 0, but it would not block, so select() is doing the right thing). The code finds all readable pipes closes them and decrements nclients. This last part of the code follows:
} else { /* set up the fd_set */ FD_ZERO(&fds); mx = 0; for (i = 0; i < maxcli; i++) { FD_SET(pipes[i], &fds); if (pipes[i] > mx) mx = pipes[i]; } select(mx+1, &fds, NULL, NULL, NULL); /* Find all closed fd's */ for (i = 0; i < maxcli; i++) { if (FD_ISSET(pipes[i], &fds)) { close(pipes[i]); pipes[i] = -1; nclients--; } } } } }
The last deduction is in case you use the fd_set to keep track of open fd's. This is ok, as long as you assume that the maximum fd value is 64 or something like that. If you traverse the fd_set from 0 to maxcli, you'll miss some. Think about it.
main(int argc, char **argv) { int sock, connection, dummy, nclients, maxcli; int p[2], i, mx; fd_set fds; Dlist pipes, tmp; maxcli = atoi(argv[1]); sock = serve_socket("plank", 5005); nclients = 0; pipes = make_dl(); while(1) { if (nclients < maxcli) { connection = accept_connection(sock); nclients++; pipe(p); if (fork() == 0) { /* Close all fds except the connection and p[1] */ close(sock); close(p[0]); dl_traverse(tmp, pipes) close(tmp->val); /* fork the grandchild and exit */ if (fork() == 0) { doserver(connection); } else { exit(0); } } else { close(connection); close(p[1]); wait(&dummy); dl_insert_b(pipes, p[0]); } } else { /* set up the fd_set */ FD_ZERO(&fds); mx = 0; dl_traverse(tmp, pipes) { FD_SET(tmp->val, &fds); if (tmp->val> mx) mx = tmp->val; } select(mx+1, &fds, NULL, NULL, NULL); /* Find all closed fd's */ dl_traverse(tmp, pipes) { if (FD_ISSET(tmp->val, &fds)) { close(tmp->val); tmp = tmp->blink; dl_delete_node(tmp->flink); nclients--; } } } } }
#define MAXFD 64 main(int argc, char **argv) { int sock, connection, dummy, nclients, maxcli; int p[2], i, mx; fd_set master, fds; maxcli = atoi(argv[1]); sock = serve_socket("plank", 5005); nclients = 0; FD_ZERO(&master); while(1) { if (nclients < maxcli) { connection = accept_connection(sock); nclients++; pipe(p); if (fork() == 0) { /* Close all fds except the connection and p[1] */ close(sock); close(p[0]); for (i = 0; i < MAXFD; i++) { if (FD_ISSET(i, &master)) close(i); } /* fork the grandchild and exit */ if (fork() == 0) { doserver(connection); } else { exit(0); } } else { close(connection); close(p[1]); wait(&dummy); FD_SET(p[0], &master); } } else { /* set up the fd_set */ memcpy(&fds, &master, sizeof(fd_set)); select(MAXFD, &fds, NULL, NULL, NULL); /* Find all closed fd's */ for (i = 0; i < MAXFD; i++) { if (FD_ISSET(i, &fds)) { close(tmp->val); FD_CLR(i, &master); nclients--; } } } } }