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--;
}
}
}
}
}