select用法&原理详解(源码剖析)(转)

今天遇到了在select()前后fd_set的变化问题,查了好久终于找到一个有用的帖子了,很赞,很详细!!原文链接如下:

select用法&原理详解(源码剖析)

我的问题是:

如下图示:在select()函数前后分别打印fdsread和fdsreaduse两个fd_set,

在gjm06-1和gjm06-2之所以一样是因为在打印前我令 fdsreaduse = fdsread; ,然后打印后,执行 selet(maxfd,&fdsreaduse,NULL,NULL,NULL); ,再分别打印两个fd_set。

我的疑惑是:前后两次fdsread都是一样的,为什么fdsreaduse打印出来就不是一样的了。

通过查看代码,发现了两次打印中间只有一个select()函数对fdsreaduse进行了操作。于是上网搜索,才明白select模型(见下摘录)。

 【注】

sockfd = 4

remotefd = 5

devfd = 6(devicefd)

我将我所需要的部分,摘录如下:

简单理解select模型

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

所以,我们可以得到select模型的特点:
(1) 文件描述符个数有限,一般来说这个数目和系统内存关系很大。select使用位域的方式来传递关心的文件描述符,位域就有最大长度。select使用位域的方式传回就绪的文件描述符,调用者需要循环遍历每一个位判断是否就绪,当文件描述符个数很多,但是空闲的文件描述符大大多于就绪的文件描述符的时候,效率很低。

(2) 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个 参数。

(3) 可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

通过以上第(5)条性质,可知:在select()时,只对devfd=6进行了write/read,故remotefd和socket都被清空了。

至此我的问题已解决。

-----------------------------------------------------------------分  割  线--------------------------------------------------------------------

此外,还有一个经典的select原理图,放在这里以便大家理解:

注:select 原理图,摘自 IBM iSeries 信息中心

 既然到这里了,就看一下select函数的原型吧

select函数原型:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
 
/*参数列表
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 
  
fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,
如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,
select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。    fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,
如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,
select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。    fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。    struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态: 第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止; 第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值; 第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
*/

头文件:

select位于: #include <sys/select.h> 

struct timeval位于: #include <sys/time.h> 

返回值:

负值:select错误
正值:某些文件可读写或出错
0:等待超时,没有可读写或错误的文件

 参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

struct timeval
{
    time_t tv_sec;//second
    time_t tv_usec;//minisecond
};

over。。。

参考:

本人墙推: Linux中select IO复用机制&使用代码实例

select处理带外数据&解决socket中多用户问题代码

原文地址:https://www.cnblogs.com/gjmhome/p/14031452.html