UNIX中的Select函数


  
  
  
  
表头文件
#i nclude<sys/time.h>
#i nclude<sys/types.h>
#i nclude<unistd.h>
定义函数
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
函数说明
select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位
参数
timeout为结构timeval,用来设置select()的等待时间,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
返回值
如果参数timeout设为NULL则表示select()没有timeout。
错误代码
执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
范例
常见的程序片段:fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}
 
 
在Linux中,我们可以使用 select 函数实现I/O端口的复用,传递给 select 函数的参数会告诉内核:
      • 我们所关心的文件描述符
      • 对每个描述符,我们所关心的状态。(我们是要想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常)
      • 我们要等待多长时间。(我们可以等待无限长的时间,等待固定的一段时间,或者根本就不等待)
    从 select 函数返回后,内核告诉我们一下信息:
      • 对我们的要求已经做好准备的描述符的个数
      • 对于三种条件哪些描述符已经做好准备.(读,写,异常)
    有了这些返回信息,我们可以调用合适的I/O函数(通常是 read 或 write),并且这些函数不会再阻塞.
?
1
2
<span style="font-family: 'courier new', courier;">    #include <sys/select.h>   
    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)</span>
    
    返回:做好准备的文件描述符的个数,超时为0,错误为 -1.
    
    首先我们先看一下最后一个参数。它指明我们要等待的时间:
?
1
2
3
4
5
<span style="font-family: 'courier new', courier;">   struct timeval
    {      
        long tv_sec;  /* 秒 */
        long tv_usec; /* 微秒 */   
    }</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
2
3
4
5
<span style="font-family: 'courier new', courier;"> #include <sys/select.h>   
int FD_ZERO(int fd, fd_set *fdset);   
int FD_CLR(int fd, fd_set *fdset);   
int FD_SET(int fd, fd_set *fd_set);   
int FD_ISSET(int fd, fd_set *fdset);</span>
    FD_ZERO宏将一个 fd_set 类型变量的所有位都设为 0,使用FD_SET 将变量的某个位置位。清除某个位时可以使用 FD_CLR,我们可以使用 FD_SET 来测试某个位是否被置位。
    当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:
?
1
2
3
4
5
<span style="font-family: 'courier new', courier;">fd_set rset;   
int fd;   
FD_ZERO(&rset);   
FD_SET(fd, &rset);   
FD_SET(stdin, &rset);</span>
     
    select 返回后,用FD_ISSET测试给定位是否置位:
?
1
2
<span style="font-family: 'courier new', courier;">if(FD_ISSET(fd, &rset)   
{ ... }</span>



下面给出一个程序的实例:

程序执行后等待用户输入。如果用户输入程序就会把它打印到屏幕上。如果用户在3秒钟未输入任何字符,程序就打印“Time out!”.

       本程序实现了一个文件描述符的非阻塞I/O

#include <sys/time.h>

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

 

int main()

{

int keyboard;

int ret=0;

char c;

fd_set readfd;

struct timeval timeout;

if((keyboard=open(“/dev/tty”,O_RDONLY|O_NONBLOCK))<0) /*打开标准输入(键盘)的文件描述符*/

    exit(1);/如果失败则退出程序*/

while(1)

{

    timeout.tv_sec=3;/*设置等待时间为3秒*/

    timeout.usec=0;

    FD_ZERO(&readfd);/*初始化描述符集*/

    FD_SET(keyboard,&readfd);/*把标准输入(键盘)加入到描述符集中*/

   

    ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);

    if(ret==0)/*如果超时打印下面的语句*/

        printf(“Time out!\n”);

    if(FD_ISSET(keyboard,&readfd))/*如果描述符集readfd的keyboard位被设置*/

    {

        read(keyboard,&c,1);/*从键盘上读如一个字符*/

        if(c==’\n’)

            continue;

        printf(“You input is %c\n”,c);

        if(c==’q’)

            break;

    }

}

}



你可能感兴趣的:(linux,unix,非阻塞,select,多个文件描述符)