Chinaunix首页 | 论坛 | 博客
  • 博客访问: 602190
  • 博文数量: 353
  • 博客积分: 1104
  • 博客等级: 少尉
  • 技术积分: 1457
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-23 23:02
个人简介

1、刚工作时做Linux 流控;后来做安全操作系统;再后来做操作系统加固;现在做TCP 加速。唉!没离开过类Unix!!!但是水平有限。。

文章存档

2015年(80)

2013年(4)

2012年(90)

2011年(177)

2010年(1)

2009年(1)

分类:

2011-10-26 10:17:56

原文地址:select()函数用法 作者:fisher0821

第一篇
Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如
connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等
待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。
可是使用Select就可以完成非阻塞(所谓非阻塞方式non-
block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
下面详细介绍一下!
Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明):
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
先说明两个结构体:
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。
fd_set集合可以通过一些宏由人为来操作,比如
清空集合FD_ZERO(fd_set *);
将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set
*);
将一个给定的文件描述符从集合中删除FD_CLR(int
,fd_set*);
检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。
第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
具体解释select的参数:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval *timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
返回值:
负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件
在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。
例子:
main()
{
    int sock;
    FILE *fp;
    struct fd_set fds;
    struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0
    char buffer[256]={0}; //256字节的接收缓冲区
    /* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开
    sock=socket(...);
    bind(...);
    fp=fopen(...); */
    while(1)
   {
        FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
        FD_SET(sock,&fds); //添加描述符
        FD_SET(fp,&fds); //同上
        maxfdp=sock>fp?sock+1:fp+1;    //描述符最大值加1
        switch(select(maxfdp,&fds,&fds,NULL,&timeout))   //select使用
        {
            case -1: exit(-1);break; //select错误,退出程序
            case 0:break; //再次轮询
            default:
                  if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据
                  {
                        recvfrom(sock,buffer,256,.....);//接受网络数据
                        if(FD_ISSET(fp,&fds)) //测试文件是否可写
                            fwrite(fp,buffer...);//写入文件
                         buffer清空;
                   }// end if break;
          }// end switch
     }//end while
}//end main
文章出处:DIY部落()
linux c语言 select函数用法
 
表头文件 #i nclude
#i nclude
#i nclude
定义函数 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 nclude
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude

int main ()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval 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))
    {
      i=read(keyboard,&c,1);
          if('\n'==c)
          continue;
      printf("hehethe input is %c\n",c);
    
       if ('q'==c)
      break;
      }
}
}
用来循环读取键盘输入

2007年9月17日,将例子程序作一修改,加上了time out,并且考虑了select得所有的情况:

#include
#include
#include
#include
#include

int main ()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1)
{
      timeout.tv_sec=5;
      timeout.tv_usec=0;
      FD_ZERO(&readfd);
      FD_SET(keyboard,&readfd);
      ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);

      //select error when ret = -1
      if (ret == -1)
          perror("select error");

      //data coming when ret>0
      else if (ret)
      {
          if(FD_ISSET(keyboard,&readfd))
          {
              i=read(keyboard,&c,1);
              if('\n'==c)
                  continue;
              printf("hehethe input is %c\n",c);

              if ('q'==c)
              break;
          }
      }

      //time out when ret = 0
      else if (ret == 0)
          printf("time out\n");
}
}


#include
#include
#include
#include

下面是我写的一个例程:
在标准输入读取9个字节数据。
用select函数实现超时判断!

int main(int argc, char ** argv)
{
char buf[10] = "";
fd_set rdfds;//
struct timeval tv; //store timeout
int ret; // return val
FD_ZERO(&rdfds); //clear rdfds
FD_SET(1, &rdfds); //add stdin handle into rdfds
tv.tv_sec = 3;
tv.tv_usec = 500;
ret = select(1 + 1, &rdfds, NULL, NULL, &tv);
if(ret < 0)
perror("\nselect");
else if(ret == 0)
printf("\ntimeout");
else
{
printf("\nret=%d", ret);
}

if(FD_ISSET(1, &rdfds))
{
printf("\nreading");
fread(buf, 9, 1, stdin); // read form stdin
}
// read(0, buf, 9); /* read from stdin */
// fprintf(stdout, "%s\n", buf); /* write to stdout */
write(1, buf, strlen(buf)); //write to stdout
printf("\n%d\n", strlen(buf));
return 0;
 
第二篇

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

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

int socket(int domain, int type, int protocol);

当然,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr,0就是stdin,1就是stdout,2就是stderr。

比如下面这两段代码都是从标准输入读入9个字节字符:

#include

#include

#include

int main(int argc, char ** argv)

{

        char buf[10] = "";

        read(0, buf, 9); /* 从标准输入 0 读入字符 */

        fprintf(stdout, "%s\n", buf); /* 向标准输出 stdout 写字符 */

        return 0;

}

/* **上面和下面的代码都可以用来从标准输入读用户输入的9个字符** */

#include

#include

#include

int main(int argc, char ** argv)

{

        char buf[10] = "";

        fread(buf, 9, 1, stdin); /* 从标准输入 stdin 读入字符 */

        write(1, buf, strlen(buf));

        return 0;

}

继续上面说的select,就是用来监视某个或某些句柄的状态变化的。select函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

struct timeval {

             long    tv_sec;         /* seconds */

             long    tv_usec;        /* microseconds */

         };

第2、3、4三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:

fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */

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

int ret; /* 保存返回值 */

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

FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */

tv.tv_sec = 1;

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

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

if(ret < 0) perror("select");/* 这说明select函数出错 */

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

else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */

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

    /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */

    if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */

        /* 读取socket句柄里的数据 */

        recv(...);

    }

}

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

int sa, sb, sc;

sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);

在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

int maxfd = 0;

if(sa > maxfd) maxfd = sa;

if(sb > maxfd) maxfd = sb;

if(sc > maxfd) maxfd = sc;

然后调用select函数:

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

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

FD_ZERO(&rdfds);

FD_SET(0, &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"); /* 在我们设定的时间tv内,用户没有按键盘 */

else { /* 用户有按键盘,要读取用户的输入 */

    scanf("%s", buf);

}

 
 
 
第3篇
(1select函数说明

总的来说,I/O处理的模型有5种:

阻塞I/O模型:若所调用的I/O函数没有完成相关的功能就会使进程挂起,直到相关数据到才会出错返回。如常见对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。

非阻塞模型:当请求的I/O操作不能完成时,则不让进程睡眠,而且返回一个错误。非阻塞I/O使用户可以调用不会永远阻塞的I/O操作,如openwriteread。如果该操作不能完成,则会立即出错返回,且表示该I/O如果该操作继续执行就会阻塞。

I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,而是让其中的一个函数等待,在这期间,I/O还能进行其它操作。如select函数和poll函数。

信号驱动I/O模型:通过按装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。这是由内核通知用户何时可以启动一个I/O操作决定的。

异步I/O模型:当一个描述符已准备好,可以启动I/O时,进程会通知内核。现在,并不是所有的系统都支持这种模型。

SelectI/O多路转接模型是处理I/O复用的一个高效方法。他可以具体设置每一个所关系的文件描述符条件、希望等待的时间等,从select函数返回时,内核会通知用户已准备好的文件描述符的数量、已准备好的条件等。通过使用select返回值,就可以调用相应的I/O处理函数。

2select函数格式

Select函数语法要点:

所需头文件:#include

                     #include

                     #include

函数原型:int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval *timeout)

函数传入值:numfds:需要检查的号码最高的文件描述符加1

                     Readfds:由select()监视的读文件描述符集合

                     Writefds:由select()监视的写文件描述符集合

                     Exeptfds:由select()监视的异常处理文件描述符集合

                     TimeoutNULL:永远等待,直到捕捉到信号或文件描述符已准备好为止

具体值:struct timeval类型的指针,若等待为timeout时间还没有文件描述符准备好,就立即返回。

0:从不等待,测试所有指定的描述符并立即返回。

函数返回值:成功:准备好的文件描述符

                     -1:出错

这里对文件描述符的处理主要涉及到4个宏函数:

FD_ZEROfd_set *set):清除一个文件描述符集

FD_SETint fdfd_set *set):将一个文件描述符加入文件描述符集中

FD_CLRint fdfd_set *set):将一个文件描述符从文件描述符集中清除

FD_ISSETint fdfd_set *set):测试该集中的一个给定位是否有变化

一般来说,在使用select函数之前,首先使用FD_ZEROFD_SET来初始化文件描述符集,在使用了select函数时,可循环使用FD_ISSET测试描述符集,在执行完成对相关后文件描述符后,使用FD_CLR来清除描述符集。

Select函数中的timeout是一个struct timeval类型的指针,如下:

Struct timeval{

              Long tv_sec:/*second*/

              Long tv_unsec;/*and microseconds(微妙)*/

}

3)使用实例

实现将hello1里的内容读出,并将此内容每隔10s写入hello2中去,建立俩个描述符集,其中一个描述符集inset1是用于读取文件内容,另一个描述符集inset2是用于写入文件的。两个文件描述符fds[0]fds[1]分别指向这一文件描述符,由于没有阻塞,所以文件描述符处于准备就绪状态。这是就分别对文件描述符fds[0]fds[1]进行读写操作。/*select.c*/

#include

#include

#include

#include

#include

int main(void)

{

       int fds[2];

       char buf[7];

       int i,rc,maxfd;

       fd_set inset1,inset2;

       struct timeval tv;

       /*首先按一定的权限打开Hello文件*/

       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);

}

阅读(449) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~