上海比较好的外包公司/seo费用
本次讲下select函数。
近日在维护公司旧项目代码时,发现了两个跟select有关的问题:一个是select()的nfds传0,第二个是timeout值while外面赋值、而while内每次select()时没有重新赋值。
这两个问题是select()手册中特地提出的两个要警惕的点。
于是有了将select()的man手册翻译一遍的想法。主要是由于在跨平台的网络编程中,select()是目前唯一的可选接口。
花费了2天时间完成本篇博客的编写。
有关windows下网络编程模型的有关知识点(知识体系),请阅读笔者的相关系列文章:
windows下并发I/O服务器模型对比(一):5种并发I/O服务器模型
windows下并发I/O服务器模型对比(二):5种并发I/O服务器模型 之二
windows下并发I/O服务器模型对比(三):Winsock六种I/O模型的性能测试及分析
windows下并发I/O服务器模型对比(四):如何选择I/O模型
windows下并发I/O服务器模型对比(五):描述winsock的I/O模型的几个角度
NAME
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexingSYNOPSIS 大纲
/* According to POSIX.1-2001 */#include <sys/select.h>/* According to earlier standards */#include <sys/time.h>#include <sys/types.h>#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);int FD_ISSET(int fd, fd_set *set);void FD_SET(int fd, fd_set *set);void FD_ZERO(fd_set *set);#include <sys/select.h>int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
pselect(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
笔者注:select和pselect的第一个参数都是nfds,字面意思是fd的个数。因为linux的文件描述符fd是逐1递增的,又是从0开始,所以形参nfds的实参都会赋值为sockfd+1。
DESCRIPTION
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without blocking.
select()和pselect()允许一个程序监视复用的文件描述符fd,为了一些种类的I/O操作(比如,输入),需要等待直到一个或多个fd变为“ready”状态。如果一个fd可以对其进行无堵塞的I/O操作(比如,read(2)),那么这个fd被认为是ready状态的。
The operation of select() and pselect() is identical, other than these three differences:
(i) select() uses a timeout that is a struct timeval (with seconds and microseconds), while pselect() uses a struct timespec (with seconds and nanoseconds).
(ii) select() may update the timeout argument to indicate how much time was left. pselect() does not change this argument.
(iii) select() has no sigmask argument, and behaves as pselect() called with NULL sigmask.
select() 和 pselect()除了以下3点不同之外,其余均完全相同:
(i) select()使用timeval结构体(包含秒和毫秒)做超时,pselect()使用timespec结构体(包含秒和纳秒)。
(ii) select()会更新timeout参数来表明距离超时到达还剩下多长时间。pselect()不会更改该参数。
(iii) select()没有sigmask参数,跟pselect()的sigmask传入NULL的行为一样。
Three independent sets of file descriptors are watched. Those listed in readfds will be watched to see if characters become available for reading (more precisely, to see if a read will not block; in particular, a file descriptor is also ready on end-of-file), those in writefds will be watched to see if a write will not block, and those in exceptfds will be watched for exceptions. On exit, the sets are modified in place to indicate which file descriptors actually changed status. Each of the three file descriptor sets may be specified as NULL if no file descriptors are to be watched for the corresponding class of events.
3个独立的fd集合被监视。readfds被监视以查看是否字符变得可读(更准确地讲,是否一次读操作不会阻塞,通常的,一个fd在end-of-file(EOF)仍然是可读的)。writefds被监视以查看是否一次写操作不会阻塞。exceptfds被监视以为了异常。在返回时,集合被适当更改以表明fd状态改变。3个fd集合中的每一个可以传值NULL,如果没有fd被监视该事件。
笔者注:当调用send()/recv()时发生错误立刻返回,已被认为是“不会阻塞”。这一点从其他的套接字函数中可以一窥。比如connect。请见如下博文:
tcp协议系列文章(8):connect:在socket上进行连接初始化
Four macros are provided to manipulate the sets. FD_ZERO() clears a set. FD_SET() and FD_CLR() respectively add and remove a given file descriptor from a set. FD_ISSET() tests to see if a file descriptor is part of the set; this is useful after select() returns. nfds is the highest-numbered file descriptor in any of the three sets, plus 1.
提供了4个宏来操作集合。FD_ZERO()清空一个集合。FD_SET() 和 FD_CLR() 可以添加一个fd到集合和从集合移除fd。FD_ISSET()监测是否一个fd在某个集合中,在select()返回后这会很有用。nfds是3个集合中所有fd数值最大值加1。
The timeout argument specifies the minimum interval that select() should block waiting for a file descriptor to become ready. (This interval will be rounded up to the system clock granularity, and kernel scheduling delays mean that the blocking interval may overrun by a small amount.) If both fields of the timeval structure are zero, then select() returns immediately. (This is useful for polling.) If timeout is NULL (no timeout), select() can block indefinitely.
参数timeout指示select()能够阻塞地等待一个fd变为ready的最小时间间隔。(这个时间间隔将会基于系统时钟粒度以四舍五入取整,内核调度延迟意味着阻塞时间间隔可能会超过一小部分。)如果timeval结构体的两个域的值都是0,那么select()会立刻返回。(这对轮询很有用。)如果timeout是NULL(即没有超时时间),那么select()会永远阻塞。
sigmask is a pointer to a signal mask (see sigprocmask(2)); if it is not NULL, then pselect() first replaces the current signal mask by the one pointed to by sigmask, then does the "select" function, and then restores the original signal mask.
sigmask是一个指向信号掩码(也称信号屏蔽字)的指针(详见sigprocmask(2))。如果它不为NULL,那么pselect()首先用sigmask替换掉当前的信号掩码,然后执行"select",再然后恢复原先的信号掩码。
Other than the difference in the precision of the timeout argument, the following pselect() call:
除了timeout参数的精度不同,如下pselect()的调用与select()的调用等价:
ready = pselect(nfds, &readfds, &writefds, &exceptfds, timeout, &sigmask);
sigset_t origmask;pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);
The reason that pselect() is needed is that if one wants to wait for either a signal or for a file descriptor to become ready, then an atomic test is needed to prevent race conditions.(Suppose the signal handler sets a global flag and returns. Then a test of this global flag followed by a call of select() could hang indefinitely if the signal arrived just after the test but just before the call. By contrast, pselect() allows one to first block signals, handle the signals that have come in, then call pselect() with the desired sigmask, avoiding the race.)
需要pselect()的原因是,如果有人想等待一个信号或一个fd变为ready状态,那么就需要一个原子试验来组织条件竞争。(假设信号处理程序设置了一个全局标志并返回。接下来,信号在该全局标志的测试之后、在select()调用之前到达,后面跟着的select()调用将永远挂起。对比地,pselect()允许一个人先阻塞信号,处理已经到来的信号,然后用期望的sigmask调用pselect(),来避免竞争。)
The time structures involved are defined in <sys/time.h> and look like
timeout参数结构体在<sys/time.h>中定义,如下:
struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};struct timespec {long tv_sec; /* seconds */long tv_nsec; /* nanoseconds */};
(However, see below on the POSIX.1-2001 versions.)
Some code calls select() with all three sets empty, nfds zero, and a non-NULL timeout as a fairly portable way to sleep with subsecond precision.
(在POSIX.1-2001版本请看下面内容。)
一些代码调用select(),传入3个空的集合,nfds为0,和一个non-NULL的timeout,来实现低于秒级别精度的可移植的sleep。
在linux,select()修改timeout来反映没有sleep的时间,大多数的其他的实现却不会这么做。(POSIX.1-2001两种都允许。)这将在如下两种情况下导致一些问题。1读取timeout的linux代码被移植到其他操作系统,2循环中反复调用select()却没有重新初始化timeout。因此要考虑select()返回后timeout没有被重新定义。
RETURN VALUE
成功时,select()和pselect()返回3个集合中所有ready状态的fd的总个数,如果超时到来还没有任何一个事件发生就会返回0。失败时,返回-1,errno被设置为相应值,此时3个集合实参和timeout实参将变得不可预料,所以在发生错误时不要依赖于它们的内容。
ERRORS
任何一个集合中包含无效fd。(比如一个已经关闭的fd,或这个fd上发生了错误。)
EINTR A signal was caught; see signal(7).
捕获到一个信号。见signal(7)。
EINVAL nfds is negative or the value contained within timeout is invalid.
nfds是负数,或timeout无效。
ENOMEM unable to allocate memory for internal tables.
内存不足,无法为内部表分配内存。
VERSIONS
pselect() was added to Linux in kernel 2.6.16. Prior to this, pselect() was emulated in glibc (but see BUGS).pselect()在linux内核2.6.16中添加。在此之前,pselect()在glibc中仿真(见BUGS)。
CONFORMING TO
select() conforms to POSIX.1-2001 and 4.4BSD (select() first appeared in 4.2BSD). Generally portable to/from non-BSD systems supporting clones of the BSD socket layer (including System V variants). However, note that the System V variant typically sets the timeout variable before exit, but the BSD variant does not.
pselect() is defined in POSIX.1g, and in POSIX.1-2001.
select()遵循POSIX.1-2001和4.4BSD(其最早见于4.2BSD)。与支持BSD套接层的非BSD系统相互移植(包括System V的变种)。然而请注意,System V变种会在其退出之前设置timeout,而BSD变种却不会那么做。
pselect()在POSIX.1g 和 POSIX.1-2001中定义。
NOTES
An fd_set is a fixed size buffer. Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined behavior. Moreover, POSIX requires fd to be a valid file descriptor.
一个fd_set的大小是固定的。用负值fd或大于等于FD_SETSIZE的fd去执行FD_CLR()或FD_SET(),将返回未定义行为。此外,POSIX要求传入有效的fd。
Concerning the types involved, the classical situation is that the two fields of a timeval structure are typed as long (as shown above), and the structure is defined in <sys/time.h>.
The POSIX.1-2001 situation is
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
where the structure is defined in <sys/select.h> and the data types time_t and suseconds_t are defined in <sys/types.h>.
考虑涉及到的类型,通常情况是,timeval结构体的两个域被定义为long型(就如同上面提到的),该结构中<sys/time.h>中定义。
POSIX.1-2001的情况是:struct timeval {time_t tv_sec; /* seconds */suseconds_t tv_usec; /* microseconds */};
该结构定义于<sys/select.h>,数据类型time_t和suseconds_t定义在<sys/types.h>。
Concerning prototypes, the classical situation is that one should include <time.h> for select(). The POSIX.1-2001 situation is that one should include <sys/select.h> for select() and pselect().
考虑到原型,通常的情况是使用select()要包含<time.h>。POSIX.1-2001的情况是使用select()和pselect()要包含<sys/select.h>。
Libc4 and libc5 do not have a <sys/select.h> header; under glibc 2.0 and later this header exists. Under glibc 2.0 it unconditionally gives the wrong prototype for pselect(). Under glibc 2.1 to 2.2.1 it gives pselect() when _GNU_SOURCE is defined. Since glibc 2.2.2 the requirements are as shown in the SYNOPSIS.
Libc4和libc5没有<sys/select.h>头文件。在glibc 2.0及之后的版本中是有该头文件的。glibc 2.0给出了错误原型的pselect()。在glibc 2.1和2.2.1,定义_GNU_SOURCE之后才能使用pselect()。自从glibc 2.2.2之后,引入的头文件就如上面SYNOPSIS中的一样。
Multithreaded applications
If a file descriptor being monitored by select() is closed in another thread, the result is unspecified. On some UNIX systems, select() unblocks and returns, with an indication that the file descriptor is ready (a subsequent I/O operation will likely fail with an error, unless another the file descriptor reopened between the time select() returned and the I/O operations was performed). On Linux (and some other systems), closing the file descriptor in another thread has no effect on select(). In summary, any application that relies on a particular behavior in this scenario must be considered buggy.
如果一个字select()中监视的fd被另一个线程关闭,其结果未做详细说明。在一些UNIX系统,select()退出阻塞并返回,且指明该fd已经ready(随后的I/O操作将很可能失败并发生错误,除非有另一个线程在select()返回后、I/O操作前将该fd重新打开)。在linux和一些其他系统中,其他线程中关闭fd不会对select()产生任何影响。总的来说,在这种情形下,任何依赖于特定行为的应用程序都必须考虑可能出现的bug。
Linux notes
The pselect() interface described in this page is implemented by glibc. The underlying Linux system call is named pselect6(). This system call has somewhat different behavior from the glibc wrapper function.
The Linux pselect6() system call modifies its timeout argument. However, the glibc wrapper function hides this behavior by using a local variable for the timeout argument that is passed to the system call. Thus, the glibc pselect() function does not modify its timeout argument; this is the behavior required by POSIX.1-2001.
The final argument of the pselect6() system call is not a sigset_t * pointer, but is instead a structure of the form:
struct {
const sigset_t *ss; /* Pointer to signal set */
size_t ss_len; /* Size (in bytes) of object pointed
to by 'ss' */
};
This allows the system call to obtain both a pointer to the signal set and its size, while allowing for the fact that most architectures support a maximum of 6 arguments to a system call.
本文中的pselect()在glibc中实现。其下层的系统调用是pselect6()。该系统调用与glibc中的包装函数的行为有些许不同。
pselect6()系统调用修改timeout参数。然而glibc包装函数,通过使用一个局部变量传递给系统调用的方法,隐藏了这一行为。因此glibc 的pselect()函数不会修改timeout参数,POSIX.1-2001要求这么做。
pselect6()系统调用的最后一个参数不是一个sigset_t指针,而是使用的如下结构:
struct {const sigset_t *ss; /* Pointer to signal set */size_t ss_len; /* Size (in bytes) of object pointedto by 'ss' */};
这允许系统调用可以包含一个指向signal set的指针和它的大小,因为大多数体系结构支持最多6个参数的系统调用。
BUGS
Glibc 2.0提供了一个不带sigmask参数的pselect()版本。
Starting with version 2.1, glibc provided an emulation of pselect() that was implemented using sigprocmask(2) and select(). This implementation remained vulnerable to the very race condition that pselect() was designed to prevent. Modern versions of glibc use the (race-free) pselect() system call on kernels where it is provided.
自2.1版本开始,glibc提供了使用sigprocmask(2)和select()实现的pselect()的仿真。原本pselect()被设计的能够防止竞争条件,但是这个实现却有能引起竞态条件的弱点。现代glibc版本使用内核提供的无竞争的pselect()系统调用。
On systems that lack pselect(), reliable (and more portable) signal trapping can be achieved using the self-pipe trick. In this technique, a signal handler writes a byte to a pipe whose other end is monitored by select() in the main program. (To avoid possibly blocking when writing to a pipe that may be full or reading from a pipe that may be empty, nonblocking I/O is used when reading from and writing to the pipe.)
在缺少pselect()的系统中,可以使用self-pipe技巧来实现可靠(和更可移植)的信号捕获。在这种技术中,一个信号处理程序写一个字节到一个管道,该管道的另一端使用select()来监视。(当写入一个已满的管道,或从一个空管道读取时,为了避免可能的阻塞,在写或读管道时会使用非阻塞I/O。)
Under Linux, select() may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks. This could for example happen when data has arrived but upon examination has wrong checksum and is discarded. There may be other circumstances in which a file descriptor is spuriously reported as ready. Thus it may be safer to use O_NONBLOCK on sockets that should not block.
在linux,select()可能报告一个socket fd “可读”,然而接下来却伴随一个读阻塞。比如,这可能发生在,当数据已经到达,但经审核该数据携带错误的校验码、并被丢弃。也有另外的情况,即一个fd被不合逻辑地上报为ready。因此在socket上使用O_NONBLOCK能更安全地避免阻塞。
On Linux, select() also modifies timeout if the call is interrupted by a signal handler (i.e., the EINTR error return). This is not permitted by POSIX.1-2001. The Linux pselect() system call has the same behavior, but the glibc wrapper hides this behavior by internally copying the timeout to a local variable and passing that variable to the system call.
在linux上,如过这次调用被一个信号处理程序中断(比如,当EINTR发生时),select()也修改timeout。这在POSIX.1-2001上是不被允许的。linux的pselect()系统调用有同样的行为,但是glibc包装函数,通过使用一个局部变量传递给系统调用的方法,隐藏了这一行为。
EXAMPLE
#include <stdio.h>#include <stdlib.h>#include <sys/time.h>#include <sys/types.h>#include <unistd.h>intmain(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 == -1)perror("select()");else if (retval)printf("Data is available now.\n");/* FD_ISSET(0, &rfds) will be true. */elseprintf("No data within five seconds.\n");exit(EXIT_SUCCESS);}
SEE ALSO
accept(2), connect(2), poll(2), read(2), recv(2), send(2), sigprocmask(2), write(2), epoll(7), time(7)
For a tutorial with discussion and examples, see select_tut(2).
COLOPHON
This page is part of release 3.53 of the Linux man-pages project. A description of the project, and information about reporting bugs, can be found at
http://www.kernel.org/doc/man-pages/