Linux下socket多路复用应用--select函数

来源:互联网 发布:c语言中的temp 编辑:程序博客网 时间:2024/06/05 22:43

Linuxsocket多路复用应用--select函数

Select系统调用是用来让我们的程序监视多个文件描述符(file descriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件描述符有某一个或多个发生了状态改变。

文件描述符在Linux里有很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件描述符被创建,如man socket可以看到“On successa file descriptor for the new socket is returned.”而man 2 open可以看到“open()and create()return the new file descriptor”,其实文件描述符就是一个整数,看socket函数的声明就明白了:

Int socketint domainint typeint protocol);

 

 

当然,我们最熟悉的文件描述符是0,1,2三个,0是标准输入,1是标准输出,2是标准错误输出。012是整数表示的,对应的FILE*结构的表示就是stdinstdoutstderr0就是stdin1就是stdout2就是stderr

select()的机制中提供一种fd_set的数据结构,实际上是一个long类型的数组,每一个数组元素都能与一个打开的文件描述符(不管是socket,还是其他文件或命名管道或设备描述符)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select的进程哪一个socket或文件可读。

非阻塞式I/O编程有两个特点:

1.如果一个发现I/O有输入,读取的过程中,另外一个也有了输入,这时候不会产生任何反应。这就需要你的程序语句去用到select函数的时候才知道有数据输入。

2.程序调用select的时候,如果没有数据输入,程序会一直等待,直到有数据为止,也就是程序中无需循环和sleep

selectsocket编程中还是比较重要的,可是对于初学socket的人来说都不太爱用select写程序,他们只是习惯写诸如connectacceptrecvrecvfrom这样的阻塞程序(所谓非阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果某个事件没有发生,进程或线程就被阻塞,函数不能立即返回)。

可是使用select就可以完成非阻塞(所谓非阻塞方式non_block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映此函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生,则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况--读写或是异常。

 

Select函数介绍

函数名:select

头文件:#include<sys/types.h>

#include<syss/times.h>

#include<sys/select.h>

函数原型:int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);

函数功能:检测文件状态变化

参数说明:1.ndfsselect监视的文件描述符数,视进程中打开文的文件数而定,一般设定为你要监视各文件中最大文件号加一。

          2.readfdsselect监视的可读文件描述符集合。

  3.writefdsselect监视的可写文件描述符集合。

  4.exceptfdsselect监视的异常文件描述符集合。

  5.timeout:本次select()的超时结束时间。(见/usr/sys/select.h

  struct timeval

  {

long tv_sec; //

long tv_unsec; //微秒

  };(可精确至百万分之一秒)

函数返回值:成功时,返回准备就绪的描述符数,若超时则返回0,失败则返回-1

 

我们在程序中药申明几个fd_set类型的变量,比如rdfdswtfdsexfds,然后把这个变量的地址&rdfds&wtfds&exfds传递给select函数。这三个参数都是一个描述符的集合,第一个rdfds是用来保存这样的描述符的:当描述符的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有描述符状态变成可写是系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即描述符有特殊情况发生时系统就会告诉select函数返回。对socket编程有用的就是readfds

几个相关的宏解释如下:

FD_ZEROfd_set* fdset:清空fdset与所有文件描述符的联系。

FD_SETint fd,fd_set* fdset):建立文件描述符fdfdset的联系。

FD_CLRint fdfd_set* fdset):清除文件描述符fdfdset的联系。

FD_ISSETint fdfd_set* fdset):检查fdset联系的文件描述符fd是否可读写,当>0表示可读写。

(关于fd_set及相关宏的定义见/usr/include/sys/types.h)。

特殊情况比如对方通过一个socket描述符发来了紧急数据。如果我们程序里只是想检测某个socket是否有数据可读,我们可以这样:

 

fd_set rdfds//先声明一个fd_set集合来保护我们要检测的socket描述符

struct timeval tv; //声明一个时间变量来保存时间

int ret; //保存返回值

FD_ZERO&rdfds);//select函数之前先把集合清零

FD_SETsocket&fdfds; //把要检测的描述符socket加入到集合里

tv.tv_sec = 1;

tv.tv_usec = 500; //设置select等待的最大时间为1秒加500毫秒

ret = select(socket+1,&rdfds,NULL,NULL,&tv); //检测我们上面设置到集合rdfds里的描述符是否有可读信息

Ifret < 0perror(“select”); //这说明select函数出错

else if(ret == 0)printf(“超时\n); //说明在我们设定的时间值1秒加500毫秒内,socket的状态没有发生变化

else //说明等待时间内,socket的状态发生了变化

{

printf(“ret = %d\n,ret; //ret这个返回值记录了发生状态变化的描述符的数目,由于这里我们只监视了socket这一个描述符,所以这里一定ret == 1,如果同时有多个描述符发生变化返回的就是描述符的总和了

//这里我们就可以从socket这个描述符里读取数据了,因为select函数已经告诉我们这个描述符里有数据可读

ifFD_ISSETsocket&rdfds))//先判断一下socket这被监视的描述符是否真的变成可读的了

{

recv() ;  //读取socket描述符里的数据

}

}

 

注意:select函数的第一个参数,是所有加入集合的描述符值得最大那个值还要加1。比如我们创建了3个描述符:

int sa,sb,sc;

sa = socket(...);

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

FD_SET(sa,&rdfds);

FD_SET(sb,&rdfds);

FD_SET(sc,&rdfds);

在使用select函数之前,一定要找到3个描述符的最大值,我们一般定义一个变量来保存最大值,取得最大socket值,算法就不用说了吧,求三个数的最大值就OK了。定义最大值变量int maxfd = 0

。。。

ret = selectmaxfd+1&rdfdsNULLNULL&tv);

同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个描述符放到select里来检测,如下:

FD_ZERO&rdfds);

FD_SET0&rdfds);

tv.tv_sec = 1;

tv.tv_usec = 0;

ret = select(1,&rdfds,NULL,NULL,&tv); //注意最大值还要加1

if(ret < 0)

perror(“select”);

else if(ret == 0)

printf(“超时\n”);

else

scanf(“%s”,buf);

 

Ok,用法已经讲的很明白了吧,接下来就是select的具体应用了,如果有兴趣的到时候可以看一下我的“TCP套接字编程实例(三)”,里面讲了利用selectTCP套接字实现异步通讯聊天。