Obviously, the client should be able to attach to this server by calling telnet:
UNIX> telnet stocks.dowjones.com 200 ...You are a summer intern at Dow Jones Industrial, and your job is to write this server. Fortunately, because you have taken CS360, this will only take you a few hours, and you can spend the rest of the summer getting paid while you pretend to work.
At the heart of this server is the subroutine broadcast_info():
broadcast_info(Dlist clients) { char line[100]; /* Read in stock price */ while(fgets(stdin, line, 100) != NULL) { /* from standard input */ for (tmp = clients->flink; tmp != clients; tmp = tmp->flink) { write(tmp->val, line, strlen(line)) { /* Write stock prices */ } /* to every client */ } }
Update broadcast_info() to work with this setup. By ``work'', I mean that it should be doing two things at once -- waiting for lines to be entered on standard input (these lines are broadcast to clients), and waiting for new clients to be announced on the pipe.
It should now look as follows:
broadcast_info(int *p, int s, Dlist clients) { ... }Broadcast_info() should still catch SIGPIPE and deal with it correctly.
main() { int mainsock, secondsock, i, j; int p[2]; Dlist clients; char c; pipe(p); if (fork() == 0) { secondsock = serve_socket("stocks.dowjones.com", 5000); clients = make_dl(); close(p[1]); broadcast_info(p, secondsock, clients); exit(0); } else { mainsock = serve_socket("stocks.dowjones.com", 200); close(0); close(p[0]); while(1) { i = accept_connection(mainsock); if (fork() == 0) { close(p[1]); j = request_connection("stocks.dowjones.com", 5000); while(read(j, &c, 1) != 0) write(i, &c, 1); exit(0); } else { close(i); write(p[1], "\n", 1); } } } }
int Sigpipe_caught; pipe_handler() { Sigpipe_caught = 1; } broadcast_info(Dlist clients) { char line[100]; signal(SIGPIPE, pipe_handler); while(fgets(stdin, line, 100) != NULL) { for (tmp = clients->flink; tmp != clients; tmp = tmp->flink) { Sigpipe_caught = 0; write(tmp->val, line, strlen(line)); if (Sigpipe_caught) { close(tmp->val); tmp = tmp->blink; dl_delete_node(tmp->flink); } } } }You can also make tmp global and delete it in the handler. Note though that you have take care in deleting the node that tmp points to the right place (the node previous to the deleted one) so that the for() loop will go the right place.
broadcast_info(int *p, int s, Dlist clients) { Dlist tmp; fd_set readset; int i; char line[100]; signal(SIGPIPE, pipe_handler); while (1) { FD_ZERO(&readset); FD_SET(0, &readset); FD_SET(p[0], &readset); select(p[0]+1, &readset, NULL, NULL, NULL); if (FD_ISSET(p[0], &readset)) { if (read(p[0], line, 1) == 0) exit(0); i = accept_connection(s); dl_insert_b(clients, i); } else { if (fgets(line, 100, stdin) == NULL) { fprintf(stderr, "Stdin closed. Bye\n"); exit(1); } for (tmp = clients->flink; tmp != clients; tmp = tmp->flink) { Sigpipe_caught = 0; write(tmp->val, line, strlen(line)); if (Sigpipe_caught) { close(tmp->val); tmp = tmp->blink; dl_delete_node(tmp->flink); } } } } }
Thus, when a client joins, the parent returns from accept_connection(), sends a newline to the broadcast_info() process, and forks off a child process which connects to the broadcast_info process via port 5000, and shuttles bytes from that process to the client. When the client quits, the child process that is doing the byte-shuttling will generate a SIGPIPE signal and die. Then the broadcast_info process will generate a SIGPIPE signal and deal with it gracefully.
Why do we need this convoluted process structure? Because the broadcast_info process cannot call select() to wait for either stuff to come in on standard input or for someone to request a connection on port 200. Therefore, we add the extra parent process to wait on port 200. Since the connection is made with the parent, and since we want a connection to be made with the broadcast_info() process, we fork off the extra process which shuttles bytes.
There is probably a better way to do this, using listen(), or perhaps even select(), but I don't know what it is -- the solution given here is comparatively simple, and is the type of thing you often end up doing in Unix. Is it inefficient? Yes. The process that shuttles bytes is something that we shouldn't have to do, but we do because it makes life easy. If we had a good thread-based model of programming supported by the operating system, we could do this much easier.
while(1) { i = accept_connection(mainsock); if (fork() == 0) { if (fork() == 0) { close(p[1]); j = request_connection("stocks.dowjones.com", 5000); while(read(j, &c, 1) != 0) write(i, &c, 1); exit(0); } else { exit(0); } } else { wait(&statusp); close(i); write(p[1], "\n", 1); }