即日起在codingBlog上分享您的技术经验即可获得积分,积分可兑换现金哦。

Linux select函数

编程语言 xiaocherry1128 29℃ 0评论
本文目录
[隐藏]

1.select函数

1.1.1、select的作用

系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

1.2.2、select的函数格式



int nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加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值,表示不关心任何文件的写变化。

fe_set* errorfds:同上面两个参数的意图,用来监视文件错误异常。

struct timeval* timeout:select的超时时间,这个参数至关重要,它可以使select处于三种状态。

timeout的三种状态:

NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。


0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。


特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。


1.3. 3、两个结构体

     第一:struct fd_set:可以理解为一个集合,这个集合中存放的是文件描述符(file
descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以,毫无疑问,一个socket就是一个文件,socket句柄就是一个文件描述符。

    第二:struct timeval:是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个毫秒数。

1.4.4、宏提供了处理这三种描述词组的方式:




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的全部位。

1.5. 5、select函数返回值

    负值:select错误。

    正值:某些文件可读写或出错。

    0:等待超时,没有可读写或错误的文件。

1.6. 6、select函数模型特点 

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。
(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个
参数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

1.7.7、select优点

           Select函数在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、
accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。



1.8.8、select缺点

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
(3)select支持的文件描述符数量太小了,默认是1024。

1.9.9、select函数编写的简易服务器


#include
#include
#include
#include
#include
#include
#include
#include
#include

int array_fds[1024];//存储文件描述符的数组
int startup(const char* _ip,int _port)
{
 int sock = socket(AF_INET,SOCK_STREAM,0);
 if(sock < 0){
  perror("socket");
  exit(1);
 }

 int flg = 1;
 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flg,sizeof(flg));

 struct sockaddr_in local;
 local.sin_family = AF_INET;
 local.sin_port = htons(_port);
 local.sin_addr.s_addr = inet_addr(_ip);
 //绑定socket
 if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
  perror("bind");
  exit(2);
 }
 //printf("bind sucess\n");
 //监听socket
 if(listen(sock,10) < 0){
  perror("listen");
  exit(3);
 }
 //printf("listen sucess\n");
 return sock;
}
static void usage(const char *proc)
{
 printf("usage:%s [local_ip],[local_port]\n",proc);
}
int main(int argc,char* argv[])
{
 if(argc != 3){
  usage(argv[0]);
  return 0;
 }

 int listen_sock = startup(argv[1],atoi(argv[2]));
 int array_size = sizeof(array_fds)/sizeof(array_fds[0]);//存放文件描述符个数
 int maxfd = 0;//指集合中文件描述符的最大值
 fd_set rfds;//监听文件描述符的读变化即是否可以从文件中读数据,如果可读返回0
 fd_set wrds;//监听文件爱你描述符的写变化,即是否可以从文件中写书据,如果可以返回0
 array_fds[0] = listen_sock;//文件描述符0号位置初始化
 //文件描述符数组中剩下元素初始为-1
 int i = 1;
 for(;i < array_size;i++){
  array_fds[i] = -1;
 }
 while(1){
  struct timeval _timeout = {0,0};//非阻塞设置{3,0}select等待3秒,3秒轮询
  FD_ZERO(&rfds);//每次循环清空集合,否则不能检查描述符的变化
  maxfd = -1;//文件描述符最大值初始化为-1

  for(i = 0; i < array_size; i++){
   if(array_fds[i] > 0){
     FD_SET(array_fds[i],&rfds);//添加描述符
    if(array_fds[i] > maxfd){
     maxfd = array_fds[i];//更新文件描述符的最大值
    }
   }
  }
  switch(select(maxfd+1,&rfds,&wrds,NULL,/*&_timeout*/NULL)){//select的使用
   case 0://再次轮询
       printf("timeout...\n");
    break;
   case -1://select错误,退出
      perror("select");
    break;
   default:
     {
    int j = 0;
    for(;j < array_size;j++){
       if(array_fds[j] < 0){
      continue;
     }
     if(j == 0 && FD_ISSET(array_fds[j],&rfds)){//测试sock是否可读,即网上是否有数据,      
      //无数据
         struct sockaddr_in client;
      socklen_t len = sizeof(client);
      int new_fd = accept(array_fds[j],(struct sockaddr*)&client,&len);
      char buf[1024];
      if(new_fd < 0){
          perror("accept");
       continue;
      }else{
       if(FD_ISSET(array_fds[j],&rfds)){//测试是否可些
        write(array_fds[j],buf,strlen(buf));//写入
       }
          printf("get a new client:(%s:%d)\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));//打印建立链接的客户端ip和port
       int k = 1;
       for(;k < array_size; k++){
          if(array_fds[k] < 0){
            array_fds[k] = new_fd;
         break;
        }
       }
       if(k == array_size){//数组已满
        close(new_fd);
       }
      }
     }else if(j != 0 && FD_ISSET(array_fds[j],&rfds)){//测试是否可读,有数据
         char buf[10240];
      ssize_t s = read(array_fds[j],buf,sizeof(buf)-1);//读取数据
      if(s > 0){
          buf[s] = 0;
        printf("client say: %s\n",buf);//打印数据
       if(FD_ISSET(array_fds[j],&wrds)){//测试是否可以写
        write(array_fds[j],buf,strlen(buf));//写入数据
        fflush(stdout);
       }
      }else if(s == 0){
          printf("client quit!\n");
        close (array_fds[j]);
       array_fds[j] = -1;
      }else{
          perror("read");//读取失败
        close(array_fds[j]);
       array_fds[j] = -1;
      }
     }
    }
   }
   break;
  }
 }
 return 0;
}





转载请注明:CodingBlog » Linux select函数

喜欢 (0)or分享 (0)
发表我的评论
取消评论

*

表情