Using Select

Use the select() function to manage a collection of sockets. The function is a Winsock derivative of the select() function in the Berkeley socket implementations and is provided for compatibility reasons for Berkeley socket applications. The function is useful on Windows CE, where the current version of Winsock does not provide asynchronous sockets and event objects. The select() function is a synchronous version of WSAAsyncSelect(), but is much more difficult to program. WSAAsyncSelect() and WSAEventSelect() are much more friendly and efficient to use than select(). However, we will give a brief description of the select() function as well as a code example to complete our coverage of communications functions.

The function responds to three events:

  • Detects data on a socket ready to read using the recv() function
  • Detects data on a socket ready to write using the send() function
  • Detects out-of-band data on sockets

How do you use the select() function to respond to these events? Let's look first at the prototype for select, which is defined in Winsock2.pas as follows:

function select(nfds: Integer; readfds, writefds, exceptfds: PFdSet; timeout: PTimeVal): Integer; stdcall;

You should ignore the first parameter, nfds, which is kept for compatibility with Berkeley socket applications. More importantly, the next three parameters are the heart of select()—readfds, writefds, and exceptfds. These are pointers to the fd_set record, which is defined in Winsock2.pas as follows:

fd_set = record fd_count: u_int; // how many are SET?

fd_array: array [0..FD_SETSIZE - 1] of TSocket; // an array of SOCKETs end;

The readfds parameter points to a collection of sockets for reading, and writefds points to a similar collection for writing. The exceptfds parameter is a pointer to a collection of sockets for out-of-band data.

Another parameter, timeout, is a pointer to the TTimeVal packed record for setting timeouts. The prototype of this data structure, defined in Winsock2.pas, is as follows:

timeval = record tv_sec: Longint; // seconds tv_usec: Longint; // and microseconds end;

TTimeVal = timeval; PTimeVal = ^timeval;

If timeout is NIL, select() will block indefinitely waiting for data on the receiving or sending sockets. If you provide values for the tv_sec and tv_usec fields, select() will wait for a number of seconds, as indicated in tv_sec, and milliseconds, as set in tv_usec. If you set these values to zero, select() will return immediately, but the code will need to poll select() frequently, which is not efficient.

Before using select(), you will need to initialize the data structures by adding socket handles to them. Winsock provides useful routines to manipulate these data structures, including initialization. These routines are in Table 5-5.

Table 5-5: Routes to manipulate data structures

Name

Description

FD_CLR

Removes the descriptor s from set.

FD_ISSET

Nonzero if s is a member of the set; zero otherwise.

_FD_SET

Adds descriptor s to set.

FD_ZERO

Initializes the set to NIL.

Below is a sequence of steps that you must perform before using select():

  • Use the FD_ZERO routine to initialize the data structures (i.e., readfds, writefds, and exceptfds).
  • Use _FD_SET to add socket handles for reading to readfds. Repeat the same procedure for socket handles for writing to writefds. In some applications, it may only be necessary to use select() on sockets for reading only, in which case you may just initialize the set of sockets for reading and ignore the set for writing. Optionally, you can add socket handles to exceptfds for out-of-band data, but in our opinion, it is poor programming practice to use out-of-band data (see the "Out-of-Band Data Etiquette" section).

The following steps show how you would use select() in a simple application:

Step 1: Call select(), and wait for I/O activity to complete. The function returns the total number of socket handles for each set of sockets.

Step 2: Using the number of socket handles returned by select(), you should call the FD_ISSET routine to check which sockets have pending I/O in what set.

Step 3: Process the sockets with pending I/O and return to Step 1 to call select() again. This scheme continues until some predefined condition is met.

There is a simple echo server example (EX55) that uses select() that you can study in Listing 5-4.

Was this article helpful?

+1 0

Post a comment