学习过程中,以下要点
- select/poll
- epoll
- nio,bio,aio
- reactor模型(libevent,nginx)
1.select
select,poll 使用循环遍历管理的socket,阻塞等待连接请求、内核可读或可写。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数:
readfds:通过FD_SET初始化为感兴趣的fds,返回可读的fd_set。可为NULL。
writefds:初始化对写感兴趣的fds,返回可写的fd_set。可为NULL。
exceptfds:初始化对异常感兴趣的fds,返回异常的fd_set。可为NULL。
注意:for循环中,select返回后,readfds,writefds,exceptfds会被改变,因此下一次调用select前,需要重新初始化readfds,writefds,exceptfds。
通过宏操作fd_set:
void FD_CLR(int fd, fd_set *set); // turn off bit for fd in fdset
int FD_ISSET(int fd, fd_set *set); // test bit for fd in fdset
void FD_SET(int fd, fd_set *set); // turn on bit for fd in fdset
void FD_ZERO(fd_set *set); // clear all bits in fdset
FD_ZERO()清空fd_set。
FD_SET()添加fd到fd_set。
FD_CLR()从fd_set中清除fd。
FD_ISSET()测试fd_set中是否含有fd,用于select返回。用于判断fd是否可读,可写。
nfds:三个fd_set中管理的fd最大值加1。
问题:为什么nfds要是最大值加1?
个人理解:fd从0开始,fd_set是一个bitset,如果fd有IO,bitset对应位置1。select内部有一个for循环遍历bitset,nfds作为for循环的边界,因此是maxfd+1。
例如,编写如下代码:
fd_set readset, writeset;
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_SET(0, &readset);
FD_SET(3, &readset);
FD_SET(1, &writeset);
FD_SET(2, &writeset);
select(4, &readset, &writeset, NULL, NULL);
因为描述符编号从0开始,所以要在最大描述符编号值上加1。
timeout:是select被阻塞等待文件描述符就绪的时间。timeout为NULL就是一直阻塞,如果timeval结构体中每个成员都是0则立即返回。
timeval结构体定义如下:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
这里有一个要注意的地方:在linux环境下,如果select阻塞后,有IO事件发生,但还没有到达timeout。那么timeout的值会被修改为剩余的时间。
这是什么意思呢?举个例子,比如select阻塞等待5s,那么timeval结构体中tv_set=5,tv_usec=0。假如阻塞等待2s后,fd可读,那么timeval会被修改成剩余时间,tv_set=3,tv_usec=0。
这就是说在循环中调用select时,调用前timeval值都需要重新设置。
select调用阻塞后,直到以下情况返回:
- 一个文件描述符就绪(可读,可写或出错)
- 被信号处理中断
- timeous到达
返回值:
返回就绪的文件描述符个数(读就绪,写就绪,错误就绪)。在这种情况下,三个描述符集中仍旧打开的位是对应于已准备好的描述符位。
如果超时时间timeout到达,仍没有感兴趣的可读,那么返回0。
出错返回-1,errno会被设置。
对于“准备好”的意思作一些更具体的说明:
(1)若对读集(readfds)中的一个描述符的read不会阻塞,则此描述符是准备好的。
(2)若对写集(wriefds)中的一个描述符的write不会阻塞,则此描述符是准备好的。
(3)若对异常条件集(exceptfds)中的一个描述符有一个未决异常条件,则此描述符是准备好的。
应该理解一个描述符阻塞与否并不影响select是否阻塞。也就是说,如果希望读一个非阻塞描述符,并且以超时值为5秒调用select,则select最多阻塞5秒。相似的,如果指定一个无限的超时值,则select阻塞到对该描述符数据准备好,或捕捉到一个信号。
如果在一个描述符上碰到了文件结束,则select认为该描述符是可读的。然后调用read,它返回0,这是UNIX指示到达文件结尾处的方法。(很多人错误的认为,当到达文件结尾处时,select会指示一个异常条件。)
------------------------摘自《UNIX环境高级编程》
注意:
fd_set是固定大小的buffer,执行FD_CLR或FD_SET时,fd如果为负或大于等于FD_SETSIZE会造成未定义行为。
linux kernel没有对fd_set大小做限制,但glibc的实现中fd_set被定义为固定大小类型,FD_SETSIZE被定义为1024。
因此select管理的socket有限制,如果管理socket的数量超过1024,只能用poll或者epoll来管理。