select函数
时间:2010-11-06 来源:sbso_1988
select
(1)select函数说明
前面的fcntl函数解决了文件的共享问题,接下来该处理I/O 复用的情况了。
总的来说,I/O 处理的模型有5 种。
· 阻塞 I/O模型:在这种模型下,若所调用的I/O 函数没有完成相关的功能就会使进程
挂起,直到相关数据到才会出错返回。如常见对管道设备、终端设备和网络设备进行读写时
经常会出现这种情况。
· 非阻塞模型:在这种模型下,当请求的I/O 操作不能完成时,则不让进程睡眠,
而且返回一个错误。非阻塞I/O 使用户可以调用不会永远阻塞的I/O 操作,如open、write
和read。如果该操作不能完成,则会立即出错返回,且表示该I/O 如果该操作继续执行
就会阻塞。
· I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,
而是让其中的一个函数等待,在这期间,I/O 还能进行其他操作。如本节要介绍的select函数
和poll函数,就是属于这种模型。
· 信号驱动 I/O 模型:在这种模型下,通过安装一个信号处理程序,系统可以自动
捕获特定信号的到来,从而启动I/O。这是由内核通知用户何时可以启动一个I/O 操作
决定的。
· 异步 I/O模型:在这种模型下,当一个描述符已准备好,可以启动I/O 时,进程会通
知内核。现在,并不是所有的系统都支持这种模型。
可以看到,select的I/O 多路转接模型是处理I/O 复用的一个高效的方法。它可以具体设
置每一个所关心的文件描述符的条件、希望等待的时间等,从select 函数返回时,内核会通
知用户已准备好的文件描述符的数量、已准备好的条件等。通过使用select 返回值,就可以
调用相应的I/O 处理函数了。
(2)select函数格式
Select函数的语法格式如表6.8 所示。
表6.8 fcntl函数语法要点
所需头文件
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
函数原型int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval
*timeout)
numfds:需要检查的号码最高的文件描述符加1
readfds:由select()监视的读文件描述符集合
writefds:由select()监视的写文件描述符集合
exeptfds:由select()监视的异常处理文件描述符集合
NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止
具体值:struct timeval类型的指针,若等待为timeout时间还没有文件描
符准备好,就立即返回
函数传入值
timeout
0:从不等待,测试所有指定的描述符并立即返回
函数返回值
成功:准备好的文件描述符
-1:出错
思考
请读者考虑一下如何确定最高的文件描述符?
可以看到,select 函数根据希望进行的文件操作对文件描述符进行了分类处理,这里,
对文件描述符的处理主要涉及到4 个宏函数,如表6.9 所示。
表6.9 select文件描述符处理函数
FD_ZERO(fd_set *set) 清除一个文件描述符集
FD_SET(int fd,fd_set *set) 将一个文件描述符加入文件描述符集中
FD_CLR(int fd,fd_set *set) 将一个文件描述符从文件描述符集中清除
FD_ISSET(int fd,fd_set *set) 测试该集中的一个给定位是否有变化
一般来说,在使用select 函数之前,首先使用FD_ZERO 和FD_SET 来初始化文件描述
符集,在使用了select 函数时,可循环使用FD_ISSET 测试描述符集,在执行完对相关后文
件描述符后,使用FD_CLR来清楚描述符集。
另外,select函数中的timeout是一个struct timeval类型的指针,该结构体如下所示:
struct timeval {
long tv_sec; /* second */
long tv_unsec; /* and microseconds*/
}
可以看到,这个时间结构体的精确度可以设置到微秒级,这对于大多数的应用而言已经
足够了。
(3)使用实例
由于 Select函数多用于I/O 操作可能会阻塞的情况下,而对于可能会有阻塞I/O 的管道、
网络编程,本书到现在为止还没有涉及。因此,本例主要表现了如何使用select 函数,而其
中的I/O 操作是不会阻塞的。
本实例中主要实现将文件hello1 里的内容读出,并将此内容每隔10s 写入hello2 中去。
在这里建立了两个描述符集,其中一个描述符集inset1 是用于读取文件内容,另一个描述符
集inset2是用于写入文件的。两个文件描述符fds[0]和fds[1]分别指向这一文件描述符。在首
先初始化完各文件描述符集之后,就开始了循环测试这两个文件描述符是否可读写,由于在
这里没有阻塞,所以文件描述符处于准备就绪的状态。这时,就分别对文件描述符fds[0]和
fsd[1]进行读写操作。该程序的流程图如图6.2 所示。
/*select.c*/
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
int fds[2];
char buf[7];
int i,rc,maxfd;
fd_set inset1,inset2;
struct timeval tv;
/*首先按一定的权限打开hello1文件*/
if((fds[0] = open ("hello1", O_RDWR|O_CREAT,0666))<0)
perror("open hello1");
/*再按一定的权限打开hello2文件*/
if((fds[1] = open ("hello2", O_RDWR|O_CREAT,0666))<0)
perror("open hello2");
if((rc = write(fds[0],"Hello!\n",7)))
printf("rc=%d\n",rc);
lseek(fds[0],0,SEEK_SET);
/*取出两个文件描述符中的较大者*/
maxfd = fds[0]>fds[1] ? fds[0] : fds[1];
/*初始化读集合inset1,并在读集合中加入相应的描述集*/
FD_ZERO(&inset1);
FD_SET(fds[0],&inset1);
/*初始化写集合inset2,并在写集合中加入相应的描述集*/
FD_ZERO(&inset2);
FD_SET(fds[1],&inset2);
tv.tv_sec=2;
tv.tv_usec=0;
/*循环测试该文件描述符是否准备就绪,并调用select函数对相关文件描述符做对应操作*/
while(FD_ISSET(fds[0],&inset1)||FD_ISSET(fds[1],&inset2)){
if(select(maxfd+1,&inset1,&inset2,NULL,&tv)<0)
perror("select");
else{
if(FD_ISSET(fds[0],&inset1)){
rc = read(fds[0],buf,7);
if(rc>0){
buf[rc]='\0';
printf("read: %s\n",buf);
}else
perror("read");
}
if(FD_ISSET(fds[1],&inset2)){
rc = write(fds[1],buf,7);
if(rc>0){
buf[rc]='\0';
printf("rc=%d,write: %s\n",rc,buf);
}else
perror("write");
sleep(10);
}
}
}
exit(0);
}