表头文件
|
#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;
}
}
}