Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses

Re: Using select and IO::Select

by tachyon (Chancellor)
on Jul 05, 2004 at 00:20 UTC ( #371763=note: print w/replies, xml ) Need Help??

in reply to Using select and IO::Select

AFAIK Perlmonks does not have pod2html functionality, but of course it only takes a couple of seconds to pod2html tut.pod > tut.html. ++ for the tutorial. It looks much nicer as HTML:


        perlselecttut - a tutorial on using select() and IO::Select


This document attempts to explain how to use the 4-arg version of select() and the module IO::Select so that they are easier to understand. If anything is unclear, the author of this document would appreciate hearing about it.

What are select()/IO::Select and why would you use them?

Without going into too much detail (none at all in fact) select() and IO::Select are useful for when you want to ``watch'' several filehandles at once to see if they are ready for reading or writing or if any of them have an exception condition. Now, when I say ``filehandles'' I really mean anything that can be read or written to as if it were a file: sockets, serial devices, pipes, fifos, etc. An example application that would use select is a chat server. Each client that is connected to the server occupies it's own socket (filehandle), so the server would use select to see what each client says having to wait on any particular client.

Caveat lector! This document is mostly written from a unix-centric point of view, but similar ideas apply on other operating systems.

All about select()

Here's the canonical select() example that you will find in the documentation:

        ($nfound,$timeleft) =
           select($rout=$rin, $wout=$win, $eout=$ein, $timeout);

But what does it all mean? Why the assignments? Why those particular variable names?

The first 3 arguments to select() tell it which filehandles to watch for reading, writing, and exceptions respectively (thus the leading r, w, and e on each variable). These arguments are each a special bitvector where each ``on'' bit corresponds to the filedescriptor number of the filehandle that is being watched. The fourth argument to select() is how long to watch the filehandles before giving up. The select() call returns a count of how many filehandles have triggered one of the reading/writing/exception conditions ($nfound) and the amount of time left in the timeout specified ($timeleft).

What are filedescriptors?

Each time you open a file, the operating system makes an entry in something called a file descriptor table so that it can keep track of the file (things like the current position, whether or not it's buffered, the contents of the buffer, etc.) By default the entry at index 0 is STDIN, at index 1 is STDOUT and STDERR is at index 2. The index into the filedescriptor table is called the file descriptor number or fileno for short. It's these indexes that are used as offsets into the bitvector to identify to select() which filehandles you wish to watch.

Using vec() to build bitvectors

A bitvector is an odd thing for perl because it's so low level. Having to fiddle with bitvectors shows how close select() is to its C heritage. But luckily perl remembers its heritage through a function called vec(). To create a bitvector for use with select() with the proper bit turned on for STDIN you would write:

        vec($vector,fileno(STDIN),1) = 1;

fileno() is a routine that returns the index into the filedescriptor table for a given filehandle (aka. the fileno). Since we know STDIN is at index 0, we could have written

        vec($vector,0,1) = 1;

But that would be extremely unportable. If, for some reason, we happened to run on a system that used 0 for some other filehandle out program wouldn't work as expected. This could happen if we closed STDIN, then opened another file. The operating system may use the first available free filedescriptor when you open a new file and since we closed STDIN, descriptor 0 would be free.

Anyway, the call to vec() means: treat $vector is a bitvector and access the bits starting at the bit position corresponding to the the index into the filedescriptor table for the filehandle STDIN for 1 bit and assign that bit the value 1. So, to watch several filehandles you would do this:

        vec($vector,fileno(FOO),1) = 1;
        vec($vector,fileno(BAR),1) = 1;
        vec($vector,fileno(BAZ),1) = 1;

and then use $vector in the call to select() to watch the FOO, BAR, and BAX filehandles.

See perldoc -f vec for more information on vec()

Why are we doing assignments in the select() call?

The reason you typically see assignments in the first three positions is that select() modifies its first 3 arguments to tell you which filehandles have data ready for reading or writing or exceptions and you usually want to continually watch the same filehandles over and over again. Since the following

        select($rout=$rin, $wout=$win, $eout=$ein, $timeout);

functions the same as

        $rout = $rin; $wout = $win; $eout = $ein;
        select($rout, $wout, $eout, $timeout);

The assignments allow $rin,$win, and $ein to keep their original values so that you can call it in a loop without having to continually build the bit vectors. Contrast these two functionally equivalent snippets:

        # Example 1, the usual idiom
        vec($rin,fileno(FOO),1) = 1;
        vec($rin,fileno(BAR),1) = 1;
        vec($rin,fileno(BAZ),1) = 1;
        while (1) {
           ($found) = select($rout=$rin,undef,undef,$timeout);
           next unless $found;
           # Check $rout to see which handles are ready for reading
        # Example 2, building the vectors each time
        while (1) {
           vec($rinout,fileno(FOO),1) = 1;
           vec($rinout,fileno(BAR),1) = 1;
           vec($rinout,fileno(BAZ),1) = 1;
           ($found) = select($rinout,undef,undef,$timeout);
           next unless $found;
           # Check $rinout to see which handles are ready for reading

Oh, btw, you'll notice that I used undef for the second and third argument to select(). If you don't care to check any filehandles for reading, writing, or exceptions you can pass undef in the respective position and select() won't bother paying attention to that condition. You can also pass c<undef> for the timeout and select() will wait forever for a filehandle to trigger the appropriate condition.

Checking which filehandles are ready

After select() returns, the three bitvectors will have changed to reflect the actual filehandles that triggered the particular condition you were waiting for. One way to check which filehandle is ready is to just use vec() again to see if the particular bit is 1. For example:

        vec($rin,fileno(FH1),1) = 1;
        vec($rin,fileno(FH2),1) = 1;
        vec($rin,fileno(FH3),1) = 1;
        while (1) {
           ($found) = select($rout=$rin,undef,undef,$timeout);
           next unless $found;
           if (vec($rout,fileno(FH1),1) == 1) {
              # There is data waiting to be read on FH1
           if (vec($rout,fileno(FH2),1) == 1) {
              # There is data waiting to be read on FH2
           # and so on ...

Another method would be to use select() again.

        vec($fh1,fileno(FH1),1) = 1;
        vec($fh2,fileno(FH2),1) = 1;
        vec($fh3,fileno(FH3),1) = 1;
        $rin = $fh1 | $fh2 | $fh3;
        while (1) {
           ($found) = select($rout=$rin,undef,undef,$timeout);
           next unless $found;
           if (select($fh1,undef,undef,$timeout)) {
              # There is data waiting to be read on FH1
           if (select($fh2,undef,undef,$timeout)) {
              # There is data waiting to be read on FH2
           # and so on ...

By building individual bitvectors for each filehandle and then combining them together using a bit-wise OR, we can check whether any of the filehandles are ready with the combined bitvector or whether an individual filahandle is ready with the individual bitvectors using select().

Note that several filehandles may be ready at once, so it would be prudent to service as many of the filehandles that you can before calling select() again.

I know which filehandles are ready. Now what?

After you have setup select() and determined which filehandles Are ready, you'll want to read from those filehandles that are ready for reading and write to those filehandles that are ready for writing and do whatever is necessary to those filehandles that have an exception condition. [ To be honest, I've never used the ability to check for exception conditions on filehandles and I have little understanding of what it may be for. The only reference I have handy at the moment, Stevens' U<Advanced Programming in the Unix Environment>, says ``... an exception condition corresponds to (a) the arrival of out-of-band data on a network connection, or (b) certain conditions occuring on a psuedo terminal that has been placed into packet mode'' ] But you must be careful how you read or write data to the filehandle. Buffered I/O like readline() (aka, the diamond operator or <>) or read(), won't work quite right, so you need to use sysread() and syswrite() to read/write from/to the appropriate filehandles.


As you can tell by now, select() isn't the friendliest of routines to use. Luckily you have another option: IO::Select. IO::Select is an object oriented interface that sits on top of the basic select() routine such that you never have to see bitvectors and strange assignments. You deal only with IO::Select objects and the filehandles themselves. Here's a simple example:

        use IO::Select;
        my $sel = IO::Select=>new;
        if (@fh = $sel->can_read($timeout)) {
           # Each filehandle in @fh is ready to be read from

The basic usage is simple: you create a IO::Select object (possibly initializing it with filehandles), then add new file handles to the object using the add() method, and when you're ready to ``watch'' the filehandles you call one of the can_read(), can_write(), or has_exception() methods on the object. Each of these methods returns an array of filehandles such that you can read/write from/to them.


 Jonathan Scott Duff



Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://371763]
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (9)
As of 2021-04-15 13:53 GMT
Find Nodes?
    Voting Booth?

    No recent polls found