select function for I/O multiplexing

    The select function allows a process to instruct the kernel to wait for any of a number of events to occur, and only wake it up after one or more events have occurred or a specified period of time has elapsed. Descriptors of interest are not limited to sockets, any descriptor can be tested using select.
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
           const struct timeval *timeout);
               /* Return value: the number of ready descriptors if there are ready descriptors, 0 if timed out, -1 if there is an error */

#define FD_SETSIZE    1024

void FD_ZERO(fd_set *fdset);             // clear all hits in fdset, initialize
void FD_SET(int fd, fd_set *fdset);      // turn on the bit for fd in fdset
void FD_CLR(int fd, fd_set *fdset);      // turn off the bit for fd in fdset
int  FD_ISSET(int fd, fd_set *fdset);    // is the bit for fd on in fdset ?

    This starts from the last parameter timeout of the select function, which tells the kernel how long to wait for any one of the specified descriptors to be ready. The parameter value has the following three possibilities:
    (1) It is a null pointer, which means to wait forever, and only return when there is a descriptor ready for I/O.
    (2) The tv_sec and tv_usec are not all 0, which means the maximum waiting time for I/O to be ready.
    (3) Both tv_sec and tv_usec are 0, which means no waiting, which is called polling.
    The wait in the first two cases is usually interrupted by a signal that the process catches during the wait and returns from the signal handler. Note that implementations do not necessarily automatically restart interrupted selects, which means that if you are catching a signal, you must be prepared to handle select returning an EINTR error for portability.
    Although POSIX specifies that this parameter has a const qualifier, some Linux distributions may modify this structure, so it should be assumed that the structure is undefined when select returns, and should be reinitialized before each call to select.
    The three parameters readset, writeset, and exceptset in the middle of select specify the descriptors that the kernel will test for read, write, and exception conditions. If you are not interested in one of the conditions, you can make it a null pointer. In fact, when all three pointers are empty, you get a more accurate timer than the sleep function.
    There are only two exception conditions currently supported:
    (1) The arrival of out-of-band data from a socket.
    (2) A pseudo terminal that has been set to grouping mode has control status information that can be read from its master terminal.
    How to specify one or more descriptor values ​​for each of these three parameters is a matter of design. select uses a descriptor set, usually an array of integers, where each bit in each integer corresponds to a descriptor, but all these implementation details are application independent, they are hidden in a data type called fd_set and FD_ZERO, FD_SET , FD_CLR and FD_ISSET in the four macros. Each bit in a descriptor set can be set or tested using these four macros, or it can be assigned to another descriptor set using an assignment statement.
    The initialization of the descriptor set is very important, because if a descriptor set allocated as an automatic variable is not initialized, unpredictable consequences may occur. The FD_ZERO macro can then be used to initialize the descriptor set.
    The first parameter maxfdp1 of select specifies the maximum number of descriptors to be tested. Its value is the maximum descriptor to be tested plus 1, and descriptors 0 to maxfdp1-1 will be tested. This parameter exists purely for efficiency reasons, as each fd_set can represent a large number of descriptors (the FD_SETSIZE constant defined in the header file <sys/select.h> is the total number of descriptors in the data type fd_set, and its value is usually 1024), However, the number used by an ordinary process is very small. The kernel improves efficiency precisely by not copying unnecessary parts of the descriptor set between the process and the kernel, thus not testing those bits that are always 0.
    The select function modifies the descriptor sets pointed to by readset, writeset, and exceptset, so each time the function is called again, the bits of interest in all descriptor sets should be set to 1 again. When calling this function, we specify the value of the descriptor we care about. After the function returns, the result will indicate which descriptors are ready, which can be tested using the macro FD_ISSET. Any bits in the descriptor set corresponding to not-ready descriptors are cleared to 0 on return.
    So what does it mean to be "ready"?
    (1) A socket is ready for reading when any of the following four conditions are met.
    a) The number of data bytes in the socket receive buffer is greater than or equal to the low water mark of the socket receive buffer. A read operation on such a socket will not block and will return a value greater than 0. The socket's low water mark can be set using the SO_RCVLOWAT socket option. The default value is 1 for TCP and UDP sockets.
    b) The read half of the connection is closed (that is, the TCP connection that received the FIN). Read operations on such a socket do not block and return 0 (ie, return EOF).
    c) The socket is a listening socket and the number of completed connections is not 0. Read operations on such sockets generally do not block.
    d) There is a socket error pending on it. Read operations on such a socket do not block and return -1, while setting errno to the exact error condition. These pending errors can also be acquired and cleared by calling getsockopt with the SO_ERROR socket option specified.
    (2) A socket is ready for writing when any of the following four conditions are met.
    a) The number of bytes of free space in the socket send buffer is greater than or equal to the socket send buffer low water mark, and either the socket is connected, or the socket does not require a connection (such as a UDP socket Character). This means that if such a socket is set to non-blocking, the write operation will not block and return a positive value. The socket's low water mark can be set using the SO_SNDLOWAT socket option. The default value is 2048 for TCP and UDP sockets.
    b) The write half of the connection is closed. Writes to such a socket will generate a SIGPIPE signal.
    c) The socket using non-blocking connect has established a connection, or the connect fails.
    d) There is a socket error pending on it. Writes to such a socket will not block and return -1, with errno set to the exact error condition. These pending errors can also be retrieved and cleared by calling getsockopt with the SO_ERROR socket option specified.
    (3) If a socket has out-of-band data or is still marked out-of-band, then it has an exception condition pending.
    The following table summarizes the above conditions that cause select to return a socket ready.

    Note that when an error occurs on a socket, it will be marked as both readable and writable by select.
    The following is the code used by the client to handle the connection part of the echo server implemented with select. It can read user input and send it to the server, and can also receive data from the server and display it to standard output. It blocks on the select call until user input or the socket is readable. The following diagram shows the various conditions handled by a call to select.

    The three conditions of the client's socket are handled as follows:
    (1) If the peer TCP sends data, the socket becomes readable, and read returns the number of bytes of data read.
    (2) If the peer TCP sends a FIN (the peer process terminates), then the socket becomes readable, and read returns 0 (EOF).
    (3) If the peer TCP sends an RST (the peer host crashes and restarts), then the socket becomes readable, and read returns -1 with the exact error code in errno.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>

#define MAX(n1, n2)	((n1)>(n2) ? (n1):(n2))

#define MAXLINE		4093

void str_cli(FILE *fp, int sockfd){
	int n;
	char buf[MAXLINE];
	fd_set readset;
	FD_ZERO(&readset);
	int stdineof = 0;
	int infd = fileno(fp);
	int maxfdp1 = MAX(infd, sockfd) + 1;
	for(;;){
		if(stdineof == 0)
			FD_SET(infd, &readset);
		FD_SET(sockfd, &readset);
		select(maxfdp1, &readset, NULL, NULL, NULL);
		if(FD_ISSET(sockfd, &readset)){		// socket is readable
			if((n=read(sockfd, buf, MAXLINE)) <= 0){
				if(n == 0 && stdineof == 1)
					return;		// normal termination
				else{
					printf("str_cli: server terminated prematurely\n");
					return;
				}
			}
			write(STDOUT_FILENO, buf, n);
		}
		if(FD_ISSET(infd, &readset)){		// input is readable
			if((n=read(infd, buf, MAXLINE)) <= 0){
				stdineof = 1;
				shutdown(sockfd, SHUT_WR);	// send FIN
				FD_CLR(infd, &readset);
				continue;
			}
			write(sockfd, buf, strlen(buf));
		}
	}
}


    In addition to select, POSIX provides a pselect variant.
#include <sys/select.h>
#include <time.h>
#include <signal.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
           const struct timespec *timeout, const sigset_t *sigmask);
               /* Return value: the number of ready descriptors if there are ready descriptors, 0 if timed out, -1 if there is an error */

    This function has two changes from select.
    (1) pselect uses the timespec structure instead of the timeval structure.
    (2) pselect adds a pointer to the signal mask sigmask as a parameter. This parameter allows the program to disable delivery of certain signals, test the global variables set by the signal handlers for those currently disabled signals, and then call pselect, telling it to reset the signal mask.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326060878&siteId=291194637