Content-type: text/html
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *utimeout);
int pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *ntimeout, sigset_t *sigmask);
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
select (or pselect) is the pivot function of most C programs that handle more than one simultaneous file descriptor (or socket handle) in an efficient manner. Its principal arguments are three arrays of file descriptors: readfds, writefds, and exceptfds. The way that select is usually used is to block while waiting for a "change of status" on one or more of the file descriptors. A "change of status" is when more characters become available from the file descriptor; or when space becomes available within the kernel's internal buffers for more to be written to the file descriptor, or when a file descriptor goes into error (in the case of a socket or pipe this is when the other end of the connection is closed).
In summary, select just watches multiple file descriptors, and is the standard Unix call to do so.
The arrays of file descriptors are called file descriptor sets. Each set is declared as type fd_set, and its contents can be altered with the macros FD_CLR, FD_ISSET, FD_SET, and FD_ZERO. FD_ZERO is usually the first function to be used on a newly declared set. Thereafter, the individual file descriptors that you are interested in can be added one by one with FD_SET. select modifies the contents of the sets according to the rules described below; after calling select you can test if your file descriptor is still present in the set with the FD_ISSET macro. FD_ISSET returns non-zero if the descriptor is present and zero if it is not. FD_CLR removes a file descriptor from the set although I can't see the use for it in a clean program.
struct timeval {
time_t tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
int child_events = 0;
void child_sig_handler (int x) {
child_events++;
signal (SIGCHLD, child_sig_handler);
}
int main (int argc, char **argv) {
sigset_t sigmask, orig_sigmask;
sigemptyset (&sigmask);
sigaddset (&sigmask, SIGCHLD);
sigprocmask (SIG_BLOCK, &sigmask,
&orig_sigmask);
signal (SIGCHLD, child_sig_handler);
for (;;) { /* main loop */
for (; child_events > 0; child_events--) {
/* do event work here */
}
r = pselect (n, &rd, &wr, &er, 0, &orig_sigmask);
/* main body of program */
}
}
Note that the above pselect call can be replaced with:
sigprocmask (SIG_BLOCK, &orig_sigmask, 0);
r = select (n, &rd, &wr, &er, 0);
sigprocmask (SIG_BLOCK, &sigmask, 0);
but then there is still the possibility that a signal could arrive after the first sigprocmask and before the select. If you do do this, it is prudent to at least put a finite timeout so that the process does not block. At present glibc probably works this way. The Linux kernel does not have a native pselect system call as yet so this is all probably much of a mute point.
So what is the point of select? Can't I just read and write to my descriptors whenever I want? The point of select is that it watches multiple descriptors at the same time and properly puts the process to sleep if there is no activity. It does this while enabling you to handle multiple simultaneous pipes and sockets. Unix programmers often find themselves in a position where they have to handle IO from more than one file descriptor where the data flow may be intermittent. If you were to merely create a sequence of read and write calls, you would find that one of your calls may block waiting for data from/to a file descriptor, while another file descriptor is unused though available for data. select efficiently copes with this situation.
A classic example of select comes from the select man page:
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void) {
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval)
printf("Data is available now.\n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.\n");
exit(0);
}
Here is an example that better demonstrates the true utility of select. The listing below a TCP forwarding program that forwards from one TCP port to another.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
static int forward_port;
#undef max
#define max(x,y) ((x) > (y) ? (x) : (y))
static int listen_socket (int listen_port) {
struct sockaddr_in a;
int s;
int yes;
if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
perror ("socket");
return -1;
}
yes = 1;
if (setsockopt
(s, SOL_SOCKET, SO_REUSEADDR,
(char *) &yes, sizeof (yes)) < 0) {
perror ("setsockopt");
close (s);
return -1;
}
memset (&a, 0, sizeof (a));
a.sin_port = htons (listen_port);
a.sin_family = AF_INET;
if (bind
(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
perror ("bind");
close (s);
return -1;
}
printf ("accepting connections on port %d\n",
(int) listen_port);
listen (s, 10);
return s;
}
static int connect_socket (int connect_port,
char *address) {
struct sockaddr_in a;
int s;
if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
perror ("socket");
close (s);
return -1;
}
memset (&a, 0, sizeof (a));
a.sin_port = htons (connect_port);
a.sin_family = AF_INET;
if (!inet_aton
(address,
(struct in_addr *) &a.sin_addr.s_addr)) {
perror ("bad IP address format");
close (s);
return -1;
}
if (connect
(s, (struct sockaddr *) &a,
sizeof (a)) < 0) {
perror ("connect()");
shutdown (s, SHUT_RDWR);
close (s);
return -1;
}
return s;
}
#define SHUT_FD1 { \
if (fd1 >= 0) { \
shutdown (fd1, SHUT_RDWR); \
close (fd1); \
fd1 = -1; \
} \
}
#define SHUT_FD2 { \
if (fd2 >= 0) { \
shutdown (fd2, SHUT_RDWR); \
close (fd2); \
fd2 = -1; \
} \
}
#define BUF_SIZE 1024
int main (int argc, char **argv) {
int h;
int fd1 = -1, fd2 = -1;
char buf1[BUF_SIZE], buf2[BUF_SIZE];
int buf1_avail, buf1_written;
int buf2_avail, buf2_written;
if (argc != 4) {
fprintf (stderr,
"Usage\n\tfwd <listen-port> \
<forward-to-port> <forward-to-ip-address>\n");
exit (1);
}
signal (SIGPIPE, SIG_IGN);
forward_port = atoi (argv[2]);
h = listen_socket (atoi (argv[1]));
if (h < 0)
exit (1);
for (;;) {
int r, n = 0;
fd_set rd, wr, er;
FD_ZERO (&rd);
FD_ZERO (&wr);
FD_ZERO (&er);
FD_SET (h, &rd);
n = max (n, h);
if (fd1 > 0 && buf1_avail < BUF_SIZE) {
FD_SET (fd1, &rd);
n = max (n, fd1);
}
if (fd2 > 0 && buf2_avail < BUF_SIZE) {
FD_SET (fd2, &rd);
n = max (n, fd2);
}
if (fd1 > 0
&& buf2_avail - buf2_written > 0) {
FD_SET (fd1, &wr);
n = max (n, fd1);
}
if (fd2 > 0
&& buf1_avail - buf1_written > 0) {
FD_SET (fd2, &wr);
n = max (n, fd2);
}
if (fd1 > 0) {
FD_SET (fd1, &er);
n = max (n, fd1);
}
if (fd2 > 0) {
FD_SET (fd2, &er);
n = max (n, fd2);
}
r = select (n + 1, &rd, &wr, &er, NULL);
if (r == -1 && errno == EINTR)
continue;
if (r < 0) {
perror ("select()");
exit (1);
}
if (FD_ISSET (h, &rd)) {
unsigned int l;
struct sockaddr_in client_address;
memset (&client_address, 0, l =
sizeof (client_address));
r = accept (h, (struct sockaddr *)
&client_address, &l);
if (r < 0) {
perror ("accept()");
} else {
SHUT_FD1;
SHUT_FD2;
buf1_avail = buf1_written = 0;
buf2_avail = buf2_written = 0;
fd1 = r;
fd2 =
connect_socket (forward_port,
argv[3]);
if (fd2 < 0) {
SHUT_FD1;
} else
printf ("connect from %s\n",
inet_ntoa
(client_address.sin_addr));
}
}
/* NB: read oob data before normal reads */
if (fd1 > 0)
if (FD_ISSET (fd1, &er)) {
char c;
errno = 0;
r = recv (fd1, &c, 1, MSG_OOB);
if (r < 1) {
SHUT_FD1;
} else
send (fd2, &c, 1, MSG_OOB);
}
if (fd2 > 0)
if (FD_ISSET (fd2, &er)) {
char c;
errno = 0;
r = recv (fd2, &c, 1, MSG_OOB);
if (r < 1) {
SHUT_FD1;
} else
send (fd1, &c, 1, MSG_OOB);
}
if (fd1 > 0)
if (FD_ISSET (fd1, &rd)) {
r =
read (fd1, buf1 + buf1_avail,
BUF_SIZE - buf1_avail);
if (r < 1) {
SHUT_FD1;
} else
buf1_avail += r;
}
if (fd2 > 0)
if (FD_ISSET (fd2, &rd)) {
r =
read (fd2, buf2 + buf2_avail,
BUF_SIZE - buf2_avail);
if (r < 1) {
SHUT_FD2;
} else
buf2_avail += r;
}
if (fd1 > 0)
if (FD_ISSET (fd1, &wr)) {
r =
write (fd1,
buf2 + buf2_written,
buf2_avail -
buf2_written);
if (r < 1) {
SHUT_FD1;
} else
buf2_written += r;
}
if (fd2 > 0)
if (FD_ISSET (fd2, &wr)) {
r =
write (fd2,
buf1 + buf1_written,
buf1_avail -
buf1_written);
if (r < 1) {
SHUT_FD2;
} else
buf1_written += r;
}
/* check if write data has caught read data */
if (buf1_written == buf1_avail)
buf1_written = buf1_avail = 0;
if (buf2_written == buf2_avail)
buf2_written = buf2_avail = 0;
/* one side has closed the connection, keep
writing to the other side until empty */
if (fd1 < 0
&& buf1_avail - buf1_written == 0) {
SHUT_FD2;
}
if (fd2 < 0
&& buf2_avail - buf2_written == 0) {
SHUT_FD1;
}
}
return 0;
}
The above program properly forwards most kinds of TCP connections including OOB signal data transmitted by telnet servers. It handles the tricky problem of having data flow in both directions simultaneously. You might think it more efficient to use a fork() call and devote a thread to each stream. This becomes more tricky than you might suspect. Another idea is to set non-blocking IO using an ioctl() call. This also has its problems because you end up having to have inefficient timeouts.
The program does not handle more than one simultaneous connection at a time, although it could easily be extended to do this with a linked list of buffers - one for each connection. At the moment, new connections cause the current connection to be dropped.
Many people who try to use select come across behavior that is difficult to understand and produces non-portable or borderline results. For instance, the above program is carefully written not to block at any point, even though it does not set its file descriptors to non-blocking mode at all (see ioctl(2)). It is easy to introduce subtle errors that will remove the advantage of using select, hence I will present a list of essentials to watch for when using the select call.
On systems that do not have a usleep function, you can call select with a finite timeout and no file descriptors as follows:
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 200000; /* 0.2 seconds */
select (0, NULL, NULL, NULL, &tv);
This is only guarenteed to work on Unix systems, however.
On success, select returns the total number of file descriptors still present in the file descriptor sets.
If select timed out, then the file descriptors sets should be all empty (but may not be on some systems). However the return value will definitely be zero.
A return value of -1 indicates an error, with errno being set appropriately. In the case of an error, the returned sets and the timeout struct contents are undefined and should not be used. pselect however never modifies ntimeout.
The poll(2) system call has the same functionality as select, but with less subtle behavior. It is less portable than select.
The pselect function is defined in IEEE Std 1003.1g-2000 (POSIX.1g). It is found in glibc2.1 and later. Glibc2.0 has a function with this name, that however does not take a sigmask parameter.