Quite often when you’re writing Linux applications, you may need to examine the state of a number of inputs to determine the next action to take. For example, a communication program such as a terminal emulator needs to read the keyboard and the serial port effectively at the same time. In a single-user system, it might be acceptable to run in a “busy wait” loop, repeatedly scanning the input for data and
reading it if it arrives. This behavior is expensive in terms of CPU time.
The select system call allows a program to wait for input to arrive (or output to complete) on a number of low-level file descriptors at once. This means that the terminal emulator program can block until there is something to do. Similarly, a server can deal with multiple clients by waiting for a request on many open sockets at the same time.
The select function operates on data structures, fd_set, that are sets of open file descriptors. A number of macros are defined for manipulating these sets:
#include
#include
void FD_ZERO(fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
As suggested by their names, FD_ZERO initializes an fd_set to the empty set, FD_SET and FD_CLR set and clear elements of the set corresponding to the file descriptor passed as fd, and FD_ISSET returns nonzero if the file descriptor referred to by fd is an element of the fd_set pointed to by fdset. The maximum number of file descriptors in an fd_set structure is given by the constant FD_SETSIZE.
The select function can also use a timeout value to prevent indefinite blocking. The timeout value is given using a struct timeval. This structure, defined in sys/time.h, has the following members:
struct timeval {
time_t tv_sec; /* seconds */
long tv_usec; /* microseconds */
}
The time_t type is defined in sys/types.h as an integral type.
The select system call has the following prototype:
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *errorfds, struct timeval *timeout);
A call to select is used to test whether any one of a set of file descriptors is ready for reading or writing or has an error condition pending and will optionally block until one is ready. The nfds argument specifies the number of file descriptors to be tested, and descriptors from 0 to nfds-1 are considered. Each of the three descriptor sets may be a null pointer, in which case the associated test
isn’t carried out.
The select function will return if any of the descriptors in the readfds set are ready for reading, if any in the writefds set are ready for writing, or if any in errorfds have an error condition. If none of these conditions apply, select will return after an interval specified by timeout. If the timeout parameter is
a null pointer and there is no activity on the sockets, the call will block forever.
When select returns, the descriptor sets will have been modified to indicate which descriptors are ready for reading or writing or have errors. You should use FD_ISSET to test them, to determine the descriptor(s) needing attention. You can modify the timeout value to indicate the time remaining until the next timeout, but this behavior isn’t specified by X/Open. In the case of a timeout occurring, all descriptor sets will be empty.
The select call returns the total number of descriptors in the modified sets. It returns –1 on failure, setting errno to describe the error. Possible errors are EBADF for invalid descriptors, EINTR for return due to interrupt, and EINVAL for bad values for nfds or timeout.
Although Linux modifies the structure pointed to by timeout to indicate the time remaining, most versions of UNIX do not. Much existing code that uses the select function initializes a timeval structure and then continues to use it without ever reinitializing the contents. On Linux, this code may operate incorrectly because Linux is modifying the timeval structure every time a timeout occurs. If you’re writing or porting code that uses the select function, you should watch out for this difference and always reinitialize the timeout. Note that both behaviors are correct; they’re just different!
Multiple Clients
Your simple server program can benefit by using select to handle multiple clients simultaneously, without resorting to child processes. For real applications using this technique, you must take care that you do not make other clients wait too long while you deal with the first to connect.
The server can use select on both the listen socket and the clients’ connection sockets at the same time. Once activity has been indicated, you can use FD_ISSET to cycle through all the possible file descriptors to discover which one the activity is on. If the listen socket is ready for input, this will mean that a client is attempting to connect and you can call accept without risk of blocking. If a client descriptor is indicated ready, this means that there’s a client request pending that you can read and deal with. A read of zero bytes will indicate that a client process has ended and you can close the socket and remove it from your descriptor set.
Try It Out An Improved Multiple Client/Server
1. For the final example, server5.c, you’ll include the sys/time.h and sys/ioctl.h headers instead of signal.h as in the last program, and declare some extra variables to deal with select:
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int result;
fd_set readfds, testfds;
2. Create and name a socket for the server:
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
3. Create a connection queue and initialize readfds to handle input from server_sockfd:
listen(server_sockfd, 5);
FD_ZERO(&readfds);
FD_SET(server_sockfd, &readfds);
4. Now wait for clients and requests. Because you have passed a null pointer as the timeout parameter, no timeout will occur. The program will exit and report an error if select returns a value less than 1:
while(1) {
char ch;
int fd;
int nread;
testfds = readfds;
printf(“server waiting\n”);
result = select(FD_SETSIZE, &testfds, (fd_set *)0,
(fd_set *)0, (struct timeval *) 0);
if(result < 1) {
perror(“server5”);
exit(1);
}
5. Once you know you’ve got activity, you can find which descriptor it’s on by checking each in turn using FD_ISSET:
for(fd = 0; fd < FD_SETSIZE; fd++) {
if(FD_ISSET(fd,&testfds)) {
6. If the activity is on server_sockfd, it must be a request for a new connection, and you add the associated client_sockfd to the descriptor set:
if(fd == server_sockfd) {
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);
printf(“adding client on fd %d\n”, client_sockfd);
}
7. If it isn’t the server, it must be client activity. If close is received, the client has gone away, and you remove it from the descriptor set. Otherwise, you “serve” the client as in the previous examples.
else {
ioctl(fd, FIONREAD, &nread);
if(nread == 0) {
close(fd);
FD_CLR(fd, &readfds);
printf(“removing client on fd %d\n”, fd);
}
else {
read(fd, &ch, 1);
sleep(5);
printf(“serving client on fd %d\n”, fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
}
In a real-world program, it would be advisable to include a variable holding the largest fd number connected (not necessarily the most recent fd number connected). This would prevent looping through potentially thousands of fds that aren’t even connected and couldn’t possibly be ready for reading.We’ve omitted it here simply for brevity’s sake and to make the code simpler.
---摘自《beginning Linux Pograming》