select 函数详解

在Linux中,我们可以使用 select 函数实现I/O端口的复用,传递给 select 函数的参数会告诉内核:
      • 我们所关心的文件描述符
      • 对每个描述符,我们所关心的状态。(我们是要想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常)
      • 我们要等待多长时间。(我们可以等待无限长的时间,等待固定的一段时间,或者根本就不等待)
    从 select 函数返回后,内核告诉我们一下信息:
      • 对我们的要求已经做好准备的描述符的个数
      • 对于三种条件哪些描述符已经做好准备.(读,写,异常)
    有了这些返回信息,我们可以调用合适的I/O函数(通常是 read 或 write),并且这些函数不会再阻塞.
1<span style="font-family: 'courier new', courier;">    #include <sys/select.h>   
2    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)</span>
    
    返回:做好准备的文件描述符的个数,超时为0,错误为 -1.
    
    首先我们先看一下最后一个参数。它指明我们要等待的时间:
1<span style="font-family: 'courier new', courier;">    struct timeval
2    {      
3        long tv_sec;   /* 秒 */
4        long tv_usec;  /* 微秒 */   
5    }</span>
    
    有三种情况:
    timeout == NULL   等待无限长的时间。等待可以被一个信号中断。当有一个描述符做好准备或者是捕获到一个信号时函数会返回。如果捕获到一个信号, select 函数将返回 -1,并将变量 erro 设为 EINTR。
    timeout->tv_sec == 0 && timeout->tv_usec == 0 不等待,直接返回。加入描述符集的描述符都会被测试,并且返回满足要求的描述符的个数。这种方法通过轮询,无阻塞地获得了多个文件描述符状态。
    timeout->tv_sec !=0 ||timeout->tv_usec != 0  等待指定的时间。当有描述符符合条件或者超过超时时间的话,函数返回。在超时时间即将用完但又没有描述符合条件的话,返回 0。对于第一种情况,等待也会被信号所中断。
    
    中间的三个参数 readset, writset, exceptset, 指向描述符集。这些参数指明了我们关心哪些描述符,和需要满足什么条件(可写,可读,异常)。一个文件描述集保存在 fd_set 类型中。fd_set 类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。如下图所示:

    
    对于 fd_set 类型的变量我们所能做的就是声明一个变量,为变量赋一个同种类型变量的值,或者使用以下几个宏来控制它:
1<span style="font-family: 'courier new', courier;"> #include <sys/select.h>   
2int FD_ZERO(int fd, fd_set *fdset);   
3int FD_CLR(int fd, fd_set *fdset);   
4int FD_SET(int fd, fd_set *fd_set);   
5int FD_ISSET(int fd, fd_set *fdset);</span>
    FD_ZERO宏将一个 fd_set 类型变量的所有位都设为 0,使用FD_SET 将变量的某个位置位。清除某个位时可以使用 FD_CLR,我们可以使用 FD_SET 来测试某个位是否被置位。
    当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:
1<span style="font-family: 'courier new', courier;">fd_set rset;   
2int fd;   
3FD_ZERO(&rset);   
4FD_SET(fd, &rset);   
5FD_SET(stdin, &rset);</span>
     
    select 返回后,用FD_ISSET测试给定位是否置位:
1<span style="font-family: 'courier new', courier;">if(FD_ISSET(fd, &rset)   
2{ ... }</span>


    

**********************************************************************************************************************************************************


摘要:在UNIX系统编程过程中,一个进程往往需要等待多个描述字发生某一事件,如可读、可写或异常等等。进程不能永远地等待其中任何单独一个描述字,它需要同时等待所有描述字,此时就是IO复用技术,系统调用select就是实现这一目标的方式之一。本文详细介绍select函数。

select广泛应用于各种场合,因为select对于任何描述字都有作用,它被应用网络程序,也被应用于终端程序,也被应用其它场合。下面我们先来看看它的函数原型:

#include<sys/select.h>
#include<sys/time.h>
int select ( int max_fd_p_1, fd_set * readset, fd_set * writeset, fd_set * exceptset, struct timeval * timeout ) ;
返回:就绪描述字的个数, 0 ——超时,- 1 ——出错

select函数有5个参数,我们会介绍每一个参数。

参数max_fd_p_1
内核需要一个数字来指定最大的描述字,因为内核使用这个参数来遍历一组描述字。又因为描述字是从0开始计数的,所以max_fd_p_1实现上是最大描述字的加上1的值。
参数readset
select可以测试一组描述字是否可读,用户需要告诉内核所有关心的描述字,readset就是用来指定关心的描述字集合。
参数writeset
如同readset指定可读描述字集合一样,writeset用来指定关心的可写描述字集合。
参数exceptset
跟readset、writeset一样,exceptset用来指定所关心的异常描述字集合,也就是当集合中一个描述字出现异常时会得到内核的一个通知。
参数timeout
用来指定超时的时长,这是一个结构体:
struct timeval
{
      long tv_sec ; //秒数
      long tv_usec ; //微秒数
}
这个结体定义在头文件sys/time.h中。

上面我们简单地针对select自身进行了说明。我们注意到,select的参数有4个是指针,如果这些指针取值是空的话会如何?对于指定描述字集合的参数,如果取空的话,它们的意义很直接,就是没有相应的关心的描述字集合。比如readset==NULL,则说明用户不关心任何一个描述字是否可读,对于writeset、exceptset也完全一样。但是对于timeout==NULL的情况就是很直接的,它表示永远等待下去,此时我们不关心需要等待多长时间,我们只要求至少有一个描述字满足用户所关心的。

对于timeout里面两个成员都取值0的时候,相应意义很直接,就是等待0秒0微秒,也就是说不等待任何时间。此时就相当于简单的轮询。

上面我们介绍了最后一个参数的,那么先前的三个参数我需要注意一下,就是它们的类型是fd_set,fd_set是什么样的类型呢?我并不知道,但有一点,就是POSIX标准为我们提供了四个宏,这四个宏可以完成我们需要对fd_set的操作,而fd_set的实际类型留给系统去定义。这四个宏分别是:

void FD_ZERO ( fd_set * set ) ; //把set设置为0
void FD_SET ( int fd, fd_set * set ) ; //从把fd添加到set
void FD_CLR ( int fd, fd_set * set ) ; //从set中删除fd
int FD_ISSET ( int fd, fd_set * set ) ; //判断fd是否在set中被设置

使用这四个宏,我们就可以完成fd_set相关的所有操作了。

select是一相十分复杂的函数,它的返回值如上所说,-1为错误,0为超时,正数为就绪的打字个数。但是事实上打字的个数往往很小,系统会一个限制,一般为1024;这是因为fd_set这个类型的限制,同时select也不应该处理过多的打字,否则话会有性能问题。

select也会设置errno值,最常见的值可能就是EINT,表示一个信号中断了select调用,作为应用程序应该在select返回错误的时候查看errno。



**********************************************************************************************************************************************************




select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,
每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他
文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,
当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执
行了select()的进程哪一Socket或文件可读。

有时,select()也被用来当作延时函数使用。sleep()延时会释放cpu,用select的话,可以在占用cpu的情况下,延时

int select(
int nfds,
fd_set* readfds,
fd_set* writefds,
fd_set* exceptfds,
const struct tim* timeout
);

参数:

nfds    

需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。

readset   

    用来检查可读性的一组文件描述字。

writeset

    用来检查可写性的一组文件描述字。

exceptset

    用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)

timeout

有三种可能:

1.       timeout="NULL"(阻塞:直到有一个fd位被置为1函数才返回)

2.       timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)

3.       timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)


返回值:     

返回对应位仍然为1的fd的总数。

四个宏来操作: 完全一点 从accept开始.


    fd_setset;

   FD_ZERO(&set);      

   FD_SET(fd,&set);   

   FD_CLR(fd,&set);   

   FD_ISSET(fd, &set);

     

过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏:


    fd_setset;

FD_ZERO(&set);     

FD_SET(0,&set);   

FD_CLR(4,&set);   


FD_ISSET(5, &set);

 

首先:
SOCKET sock;
sock= socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_inaddr;     //告诉sock 应该再什么地方licence
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(11111);  //端口啦
addr.sin_addr.s_addr=htonl(INADDR_ANY);           //在本机的所有ip上开始监听

bind (sock,(sockaddr*)&addr,sizeof(addr));//bind....

listen(sock,5);                              ;//最大5个队列

SOCKETsocka;                        //这个用来接受一个连接
fd_setrfd;                                  // 描述符集 这个将用来测试有没有一个可用的连接
struct tim timeout;

FD_ZERO(&rfd);                    //总是这样先清空一个描述符集

timeout.tv_sec=60;               //等下select用到这个
timeout.tv_usec=0;

u_long ul="1";

ioctlsocket(sock,FIONBIO,&ul);   //用非阻塞的连接

//现在开始用select
FD_SET(sock,&rfd);   //把sock放入要测试的描述符集 就是说把sock放入了rfd里面这样下一步调用select对rfd进行测试的时候就会测试sock了(因为我们将sock放入的rdf)一个描述符集可以包含多个被测试的描述符,
if(select(sock+1,&rfd,0,0,&timeout)==0)  // select的第一个参数是可以忽略的(这样写是为了保持和linux下一致)第二个参数放入需要测试的读描述符集(也就是说如果这里面有一个描述符可以读取了,select就返回)第三个放入需要测试的写描述符集,第四个放入"可执行描述符集"(??我也不知道)第五个参数是超时时间(如果过了这个超时时间依然没有描述符准备好,select也返回.(如果为NULL,那就一直等到一个描述符集变成准备好的状态)
{ //这个大括号接上面的,返回0那么就超过了timeout预定的时间

//处理....

}

if(FD_ISSET(sock,&rfd))
    //有一个描述符准备好了

socka=accept(sock,0,0);    //好了 接受它吧

//你还要判断一下socka是不是有效的socket才行....

-------------------------------------------------------------------------------------------------------------------------------

一般的情况下

假设你要判断两个socket 是否可读可写 那就这样:

假设 socka 和sockb 是两个socket他们已经被连接上,并且能够收发数据

fd_set rfd,wfd;//一个用来测试读 一个用来测试写

FD_ZERO(&rfd);

FD_ZERO(&wfd);

FD_SET(socka,&rfd);//把socka放入读描述符集

FD_SET(sockb,&rfd);//把sockb放入读描述符集

FD_SET(socka,&wfd);把socka放入写描述符集

FD_SET(sockb,&wfd);把sockb放入写描述符集

if(SOCKET_ERROR!=select(0,&rfd,&wfd,0,0))     //测试这两个描述符集,永不超时 其中rfd只用来测试读 wfd只用来测试写

    //没有错误

if(FD_ISSET(socka,&rfd))   //socka可读

{...}

if(FD_ISSET(sockb,&rfd)  //sockb可读

{...}

if(FD_ISSET(socka,&wfd) //socka可写

{...}

if(FD_ISSET(sockb,&wfd)//sockb可写

{...}

}


.............................................................................................


下面是linux环境下select的一个简单用法

#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude

int main ()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct tim timeout;
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1)
    {
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(FD_ISSET(keyboard,&readfd))
    {
     i="read"(keyboard,&c,1);
         if(''\n''==c)
         continue;
     printf("hehethe input is %c\n",c);
    
      if (''q''==c)
     break;
     }
}
}
用来循环读取键盘输入



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值