Jérôme Belleman
Home  •  Tools  •  Posts  •  Talks  •  Travels  •  Graphics  •  About Me

Watching Standard Input for Data in C

15 Feb 2009

This post describes a recipe written in C around the select() function to wait for data on stdin, eventually timing out if it takes too long to arrive.

1 In a Nutshell

The way to go is to use the select() function. In this example we want to have select() repeatedly keep a watch on stdin and see if there's something to nibble, in which case it will be given to read(). If a timeout is reached, report it and bail out. Do the same if an error occurs.

2 Code

  1. Headers – According to the man page, select() is declared in sys/select.h but it works without including it. And the man page example doesn't include it either. (It seems to be included in sys/time.h).

    #include <sys/time.h>   /* timeval */
  2. File descriptors – The select() function works on sets of file descriptors. A set should be cleared with the FD_ZERO macro before you can add new descriptors to it with the FD_SET macro:

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    Here, we only add stdin (0) to the set.
  3. Timeouts – Use a struct timeval to set the timeout:

    struct timeval tv;
    tv.tv_sec = 10;
    tv.tv_usec = 0;
  4. Repeatedly read stdin – Have select() keep a weather eye out and call read() when there's something sensible to read:

    while (1) {
        retval = select(1, &fds, NULL, NULL, &tv);
        if (retval == -1) {
            perror("select");
            return 1;
        } else if (retval == 0) {
            printf("timeout\n");
            return 1;
        } else {
            printf("ok\n");
            read(0, &buf, 1); 
        }   
    }
    • The first argument is the highest-numbered file descriptor of all three sets + 1. Apparently you're better off setting it to FD_SETSIZE. The two next arguments, here set to NULL, are respectively writing and exception sets. The last argument is the timeval.
    • If the value returned by select() is -1, something went wrong and errno is set. If the return value is 0, the timeout has been reached. This is not considered an error, mind you. In any other case, something came up to read. You then have to call read(), otherwise select() will pass again immediately for the same reason. See it as a queue where select() chirps cheerfully when there's something to read and will keep doing so as long as read() hasn't read all there's to read in the queue.
    • There's one funny thing with the Linux implementation of select(): the timeval is altered so that the next timeout is as short as the previous select() had to wait. So if you want the timeout to always be the same, give its value after each call to select().

3 References