在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> |
2 | int FD_ZERO( int fd, fd_set *fdset); |
3 | int FD_CLR( int fd, fd_set *fdset); |
4 | int FD_SET( int fd, fd_set *fd_set); |
5 | int 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; |
2 | int fd; |
3 | FD_ZERO(&rset); |
4 | FD_SET(fd, &rset); |
5 | FD_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/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 ; //微秒数
}
上面我们简单地针对select自身进行了说明。我们注意到,select的参数有4个是指针,如果这些指针取值是空的话会如何?对于指定描述字集合的参数,如果取空的话,它们的意义很直接,就是没有相应的关心的描述字集合。比如readset==NULL,则说明用户不关心任何一个描述字是否可读,对于writeset、exceptset也完全一样。但是对于timeout==NULL的情况就是很直接的,它表示永远等待下去,此时我们不关心需要等待多长时间,我们只要求至少有一个描述字满足用户所关心的。
对于timeout里面两个成员都取值0的时候,相应意义很直接,就是等待0秒0微秒,也就是说不等待任何时间。此时就相当于简单的轮询。
上面我们介绍了最后一个参数的,那么先前的三个参数我需要注意一下,就是它们的类型是fd_set,fd_set是什么样的类型呢?我并不知道,但有一点,就是POSIX标准为我们提供了四个宏,这四个宏可以完成我们需要对fd_set的操作,而fd_set的实际类型留给系统去定义。这四个宏分别是:
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.
2.
3.
返回值:
返回对应位仍然为1的fd的总数。
四个宏来操作: 完全一点 从accept开始.
过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏:
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;
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(11111);
addr.sin_addr.s_addr=htonl(INADDR_ANY);
bind (sock,(sockaddr*)&addr,sizeof(addr));//bind....
listen(sock,5);
SOCKETsocka;
fd_setrfd;
struct tim timeout;
FD_ZERO(&rfd);
timeout.tv_sec=60;
timeout.tv_usec=0;
u_long ul="1";
ioctlsocket(sock,FIONBIO,&ul);
//现在开始用select
FD_SET(sock,&rfd);
if(select(sock+1,&rfd,0,0,&timeout)==0)
{ //这个大括号接上面的,返回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))
{
if(FD_ISSET(socka,&rfd))
{...}
if(FD_ISSET(sockb,&rfd)
{...}
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))
}
}
用来循环读取键盘输入