CS360 Lecture notes -- Select


Select()

Select() is a system call that lets you probe the operating system to discern whether or not there is I/O to be done on various file descriptors.

For example, suppose that we want to write jtelnet. This is a program that works just like "telnet" (for the most part). The user specifies a hostname and a port, and jtelnet performs a request_connection() to that hostname and port.

Once the connection is made, jtelnet will take any bytes that come from the socket connection and print them out on standard output, and it will take any input from standard input, and write it onto the socket connection.

Jtelnet needs select() because it needs to probe the operating system to discern whether there are bytes to be read from stdin or from the socket connection.

The arguments to select() are as follows:

int select (int width, fd_set *readfds, 
                       fd_set *writefds, 
                       fd_set *exceptfds, struct timeval *timeout)
The data types fd_set and struct timeval are defined in sys/types.h and sys/time.h. In this class, we won't care about selecting for writable fd's or for exceptional conditions, so we will always use select() as follows:
select(width, readfds, NULL, NULL, NULL);
Readfds is a bitmap of file descriptors. Select() will return when any of the file descriptors in the bitmap has something to read. When it returns, readfds will be a new bitmap containing all the file descriptors that have something read. In other words, a read() call on that file descriptor is guaranteed not to block. It may return with a value of zero (i.e. end of file), but it is guaranteed to return.

We manipulate the bitmaps with the macros:

     FD_SET (fd, &fdset)
     FD_CLR (fd, &fdset)
     FD_ISSET (fd, &fdset)
     FD_ZERO (&fdset)
where fd is an int and fdset is a fd_set. So, to make a bitmap that has file descriptors 0 and 3 set, we would do:
  fd_set fds;

  FD_ZERO(&fds);
  FD_SET(0, &fds);
  FD_SET(3, &fds);
We use FD_CLR() to clear bits from the bitmap, and FD_ISSET() to test to see if a particular bit is set.

Now, jtelnet (in jtelnet.c):

First, we mess with argc and argv and do the requisite request_connection:

#include "sockettome.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>

main(int argc, char **argv)
{
  int port;
  int fd;
  char *hostname, buf[100];
  fd_set readfds, masterfds;                            
  int nopen, n;

  if (argc != 3) {
    fprintf(stderr, "usage: jtelnet host port\n");
    exit(1);
  }

  hostname = argv[1];
  port = atoi(argv[2]);

  fd = request_connection(hostname, port);

Now, we are going to use two fd_sets -- a permanent one called masterfds, and a temporary one called readfds. First, we initialize masterfds so that fd zero and the socket connection are set:

  FD_ZERO(&masterfds);
  FD_SET(0, &masterfds);
  FD_SET(fd, &masterfds);

Now, we go through an infinite loop that first copies masterfds to readfds, then calls select() on readfds, and then performs the correct action (sending stdin to the socket, or input from the socket to stdout) depending on what bits of readfds select() sets. An EOF from either stdin or from the socket ends the telnet session.

  while (1) {
    memcpy(&readfds, &masterfds, sizeof(fd_set));

    if (select(fd+1, &readfds, NULL, NULL, NULL) < 0) {
      perror("on select");
      exit(1);
    }

    if (FD_ISSET(0, &readfds)) {
      n = read(0, buf, 100);
      if (n == 0) {
        close(0);
        close(fd);
        exit(1);
      } else {
        write(fd, buf, n);
      }
    } 

    if (FD_ISSET(fd, &readfds)) {
      n = read(fd, buf, 100);
      if (n == 0) {
        close(0);
        close(fd);
        exit(1);
      } else {
        write(1, buf, n);
      }
    }

  }
}

Telnet is a nice program because it is a generalized client. Back in the days before the web and massive security concerns, people wrote servers to which clients would telnet. For example, as of the most recent revision of these lecture notes (April, 2010), there is a weather server at host rainmaker.wunderground.com and port 3000. If you telnet to it (or jtelnet), you can get current weather conditions. This is a very powerful paradigm. Telnet is nice because it allows the people at Michigan to set up a server without providing client code -- we already have the client: telnet.

Of course, now we have the web, which is made possible by protocols to write general graphical clients with hypertext. That's a little beyond the scope of this lecture....