Suppose you are asked to write an echo server that can also respond to interactive commands that the user types to standard input. In this case, the server must respond to two independent I/O events: (1) a network client making a connection request, and (2) a user typing a command line at the keyboard. Which event do we wait for first? Neither option is ideal.
One solution to this dilemma is a technique called I/O multiplexing. The basic idea is to use the select function to ask the kernel to suspend the process, returning control to the application only after one or more I/O events have occurred. The code below shows how we might use select to implement an iterative echo server that also accepts user commands on the standard input.
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
int port = atoi(argv[1]);
socklen_t clientlen = sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
int listenfd = open_listenfd(port);
fd_set read_set, ready_set;
FD_ZERO(&read_set); // Clear read set
FD_SET(STDIN_FILENO, &read_set); // Add stdin to read set
FD_SET(listenfd, &read_set); // Add listenfd to read set
while (1) {
ready_set = read_set;
select(listenfd+1, &ready_set, NULL, NULL, NULL);
if (FD_ISSET(STDIN_FILENO, &ready_set))
command(); // Read command line from stdin
if (FD_ISSET(listenfd, &ready_set)) {
int connfd = accept(listenfd, (SA *) &clientaddr, &clientlen);
echo(connfd); // Echo client input until EOF
close(connfd);
}
}
}
One advantage of event-driven programming based on I/O multiplexing is that event-driven designs give programmers more control over the behavior of their programs than process-based designs. Another advantage is that an event-driven server based on I/O multiplexing runs in the context of a single process, and thus every logical flow has access to the entire address space of the process. This makes it easy to share data between flows. A related advantage of running as single process is that you can debug your concurrent server as you would any sequential program, using a familiar debugging tool such as GDB. Finally, event-driven designs are often significantly more efficient than process-based designs because they do not require a process context switch to schedule a new flow.
A significant disadvantage of event-driven designs is coding complexity. Our event-driven concurrent echo server requires three times more code than the process-based server. Another significant disadvantage of event-based designs is that they cannot fully utilize multi-core processors.