【转载】select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET

来源:互联网 发布:知柏地黄丸哪家好 编辑:程序博客网 时间:2024/06/03 17:47

http://hi.baidu.com/%B1%D5%C4%BF%B3%C9%B7%F0/blog/item/e7284ef16bcec3c70a46e05e.html

select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型:       

[cpp] view plain copy
  1. #include <sys/time.h>   
  2. #include <unistd.h>   
  3. int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);  

    参数maxfd是需要监视的最大的文件描述符值+1;

    rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。

    struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
    fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作:
     FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
     FD_SET(fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。 
     FD_CLR(fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。 
     FD_ISSET(int fd,fd_set *fdset);用于测试指定的文件描述符是否在该集合中。

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

[cpp] view plain copy
  1. fd_set set;  
  2. FD_ZERO(&set);       
  3. FD_SET(0, &set);     
  4. FD_CLR(4, &set);       
  5. FD_ISSET(5, &set);     
―――――――――――――――――――――――――――――――――――――――
注意fd的最大值必须<FD_SETSIZE。
―――――――――――――――――――――――――――――――――――――――

2、select函数的接口比较简单:

[cpp] view plain copy
  1. int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct tim *timeout);  

功能:

    测试指定的fd可读?可写?有异常条件待处理?     
参数:
    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
    用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。 
    有三种可能:
      1.timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件)
      2.timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)
      3.timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生)

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

Remarks:
    三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。

举个例子,比如recv(),   在没有数据到来调用它的时候,你的线程将被阻塞,如果数据一直不来,你的线程就要阻塞很久.这样显然不好. 所以采用select来查看套节字是否可读(也就是是否有数据读了)  
步骤如下——   

[cpp] view plain copy
  1. socket   s;     
  2. .....     
  3. fd_set   set;     
  4. while(1)     
  5. {         
  6.       FD_ZERO(&set);//将你的套节字集合清空     
  7.       FD_SET(s,   &set);//加入你感兴趣的套节字到集合,这里是一个读数据的套节字s     
  8.       select(0,&set,NULL,NULL,NULL);//检查套节字是否可读,     
  9.                                                         //很多情况下就是是否有数据(注意,只是说很多情况)    
  10.                                                         //这里select是否出错没有写     
  11.       if(FD_ISSET(s,   &set)   //检查s是否在这个集合里面,     
  12.       {                                           //select将更新这个集合,把其中不可读的套节字去掉     
  13.                                                   //只保留符合条件的套节字在这个集合里面                           
  14.               recv(s,...);     
  15.       }     
  16.       //do   something   here     
  17. }  

    理解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)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务 器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。本人对调整fd_set的大小不太感兴趣,参考
http://www.cppblog.com /CppExplore/archive/2008/03/21/45061.html中的模型2(1)可以有效突破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判断是否有时间发生)。
    下面给一个伪码说明基本select模型的服务器模型:

[cpp] view plain copy
  1. array[slect_len];  
  2. nSock=0;  
  3. array[nSock++]=listen_fd;(之前listen port已绑定并listen)  
  4. maxfd=listen_fd;  
  5. while{  
  6.    FD_ZERO(&set);  
  7.    foreach (fd in array)   
  8.    {  
  9.        fd大于maxfd,则maxfd=fd  
  10.        FD_SET(fd,&set)  
  11.    }  
  12.    res=select(maxfd+1,&set,0,0,0);  
  13.    if(FD_ISSET(listen_fd,&set))  
  14.    {  
  15.        newfd=accept(listen_fd);  
  16.        array[nsock++]=newfd;  
  17.             if(--res=0) continue  
  18.    }  
  19.    foreach 下标1开始 (fd in array)   
  20.    {  
  21.        if(FD_ISSET(fd,&set))  
  22.           执行读等相关操作  
  23.           如果错误或者关闭,则要删除该fd,将array中相应位置和最后一个元素互换就好,nsock减一  
  24.              if(--res=0) continue  
  25.    }  
  26. }  

    使用select函数的过程一般是:
    先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。

    以下是一个测试单个文件描述字可读性的例子:

[cpp] view plain copy
  1. int isready(int fd)  
  2. {  
  3.     int rc;  
  4.     fd_set fds;  
  5.     struct tim tv;      
  6.     FD_ZERO(&fds);  
  7.     FD_SET(fd,&fds);  
  8.     tv.tv_sec = tv.tv_usec = 0;      
  9.     rc = select(fd+1, &fds, NULL, NULL, &tv);  
  10.     if (rc < 0)   //error  
  11.     return -1;      
  12.     return FD_ISSET(fd,&fds) ? 1 : 0;  
  13. }  

    下面还有一个复杂一些的应用:
    //这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd
[cpp] view plain copy
  1. uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)      
  2. {  
  3.      fd_set rfds,wfds;  
  4. #ifdef _WIN32  
  5.      TIM tv;  
  6. #else  
  7.      struct tim tv;  
  8. #endif      
  9.      FD_ZERO(&rfds);  
  10.      FD_ZERO(&wfds);   
  11.      if (rd)     //TRUE  
  12.      FD_SET(*s,&rfds);   //添加要测试的描述字   
  13.      if (wr)     //FALSE  
  14.        FD_SET(*s,&wfds);   
  15.      tv.tv_sec=timems/1000;     //second  
  16.      tv.tv_usec=timems%1000;     //ms   
  17.      for (;;) //如果errno==EINTR,反复测试缓冲区的可读性  
  18.           switch(select((*s)+1,&rfds,&wfds,NULL,  
  19.               (timems==TIME_INFINITE?NULL:&tv))) //测试在规定的时间内套接口接收缓冲区中是否有数据可读  
  20.          {                                              //0--超时,-1--出错  
  21.          case 0:      
  22.               return 0;   
  23.          case (-1):     
  24.               if (SocketError()==EINTR)  
  25.                    break;                
  26.               return 0; //有错但不是EINTR   
  27.           default:  
  28.               if (FD_ISSET(*s,&rfds)) //如果s是fds中的一员返回非0,否则返回0  
  29.                    return 1;  
  30.               if (FD_ISSET(*s,&wfds))  
  31.                    return 2;  
  32.               return 0;  
  33.          };  
  34. }  
阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 喊麦歌词 喊麦技巧 天佑喊麦 喊麦dj 喊麦网 喊麦现场 喊麦大全 dj喊麦 dj喊麦网 伤感喊麦 洪磊喊麦 喊麦伴奏 阿哲喊麦 经典喊麦 喊麦dj网站 什么是喊麦 喊麦是什么 另类喊麦 好听的喊麦 女生喊麦 九局喊麦 喊麦教程 喊麦培训 喊麦软件 酒吧喊麦 dj喊麦现场 ktv喊麦 喊麦dj舞曲 dj现场喊麦 喊麦开场词 喊麦另类 流行喊麦 原创喊麦 喊麦dj歌曲 喊麦的歌曲 天佑喊麦歌曲大全 喊麦大全100首 喊麦歌曲大全 喊麦歌词大全100首 喊麦歌曲大全100首 小仙儿dj喊麦歌曲大全