IO多路复用学习(1) select

学习过程中,以下要点   

  1. select/poll
  2. epoll
  3. nio,bio,aio
  4. 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来管理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值