I/O多路转接之select服务器

来源:互联网 发布:什么是算法的复杂性 编辑:程序博客网 时间:2024/06/06 00:51

(1)每次调⽤用select,都需要把fd集合从⽤用户态拷贝到内核态,这个开销在fd很多时会很⼤大 (2)同时每次调⽤用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很⼤大 (3)select⽀支持的⽂文件描述符数量太⼩小了,默认是1024 select 服务器
发生了改变就是读事件写事件就绪
文件描述符通常关心读事件、写事件、异常事件
也可以关心至少一个或者有多个事件
select是系统调用接口
只负责等,一次等多个文件描述符就绪之后select就会返回通知上层

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select的参数:
nfds:等多个文件描述符中最大的加一
后四个参数都是输入输出型参数
输入型:哪些文件描述符关心对应类型的事件
输出型:哪些你所关心的文件描述符对应得事件已经发生了

rdset,wrset,exset分别对应于需要检测的可读⽂文件描述符的集合,可写⽂文件描述符的集 合及异 常⽂文件描述符的集合。
struct timeval结构⽤用于描述⼀一段时间长度,如果在这个时间内,需要监视的描述符没有事件 发⽣生则函数返回,返回值为0。

下⾯面的宏提供了处理这三种描述词组的⽅方式: 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()的等待时间,其结构定义如下:

如果参数timeout设为: NULL:则表⽰示select()没有timeout,select将⼀一直被阻塞,直到某个⽂文件描述符上发⽣生了 事件。 0:仅检测描述符集合的状态,然后⽴立即返回,并不等待外部事件的发⽣生。 特定的时间值:如果在指定的时间段⾥里没有事件发⽣生,select将超时返回

函数返回值: 执行成功则返回文件描述词状态已改变的个数 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回; 当有错误发⽣生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和 timeout的值变成不可预测。错误值可能为: EBADF ⽂文件描述词为⽆无效的或该⽂文件已关闭 EINTR 此调⽤用被信号所中断 EINVAL 参数n 为负值。 ENOMEM 核⼼心内存不⾜足 常见的程序⽚片段如下: fs_set readset; FD_SET(fd,&readset); select(fd+1,&readset,NULL,NULL,NULL); if(FD_ISSET(fd,readset)){…⋯…⋯}

理解select模型:
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每⼀一bit 可以对应⼀一个⽂文件描述符fd。则1字节长的fd_set最⼤大可以对应8个fd。

(1)执⾏行fd_set set; FD_ZERO(&set);则set⽤用位表⽰示是0000,0000。
(2)若fd=5,执⾏行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加⼊入fd=2,fd=1,则set变为0001,0011
(4)执⾏行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发⽣生可读事件,则select返回,此时set变为0000,0011。注意:没有事件 发⽣生的fd=5被清空。 基于上⾯面的讨论,可以轻松得出select模型的特点:
(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务 器上sizeof(fd_set)= 512,每bit表示一个⽂文件描述符,则我服务器上⽀支持的最⼤大⽂文件描述符是512*8=4096。据说 可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。
(1)可以有效突破select可监控的⽂文件描述符上 限。
(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判断是否有时间发⽣生)。

#include<stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/time.h>#include <sys/types.h>#include <unistd.h>#include<arpa/inet.h>#include<netinet/in.h>#include<string.h>#include<unistd.h>#include<stdlib.h>int fds[sizeof(fd_set)*8];   static void Usage(char*proc){   printf("%s [local_ip] [local port]\n");}int startUp(char*ip,int port){   int sock=socket(AF_INET,SOCK_STREAM,0);    if(sock<0)    {        perror("socket");        exit(2);    }    struct sockaddr_in server;    server.sin_family=AF_INET;    server.sin_port=htons(atoi(port));    server.sin_addr.s_addr=inet_addr(ip);    if(bind(sock,(struct sockaddr*)&server,sizeof(struct sockaddr_in))<0)    {        perror("bind");        exit(3);    }    if(listen(sock,10)<0)    {        perror("listen");        exit(4);    }    return sock;}int main(int argv,char*argc[]){    if(argv!=3)    {        Usage(argc[0]);        return 1;    }    int listen_sock=startUp(argc[1],argc[2]);    int nums=sizeof(fd_set)*8;    fd_set rds;    int i=0;    for(i=0;i<nums;i++)    {        fds[i]=-1;    }    while(1)    {      int max=-1;      struct timeval timeout={5,0};      fds[0]=listen_sock;      for(i=0;i<nums;i++)      {          if(fds[i]>-1)          {              FD_SET(fds[i],&rds);//在rdss设置所要关心的文件描述符对应的事件          }          if(max<fds[i])          {              max=fds[i];          }      }      switch(select(max+1,&rds,NULL,NULL,&timeout))// 执行成功则返回文件描述词状态已改变的个数       {          case 0:              printf("timeout...\n");              break;          case -1:              perror("select");              break;        default:              for(i=0;i<nums;i++)              {                   if(i==0&&FD_ISSET(fds[i],&rds))//判断listen_sock描述符上对应的事件是否就绪                  {                      struct sockaddr_in client;                      socklen_t len=sizeof(client);                      int  newsock=accept(listen_sock,(struct socketaddr*)&client,&len);//继续取出将要关心的对应的文件描述符上的对应的事件                      if(newsock<0)                      {                          perror("accept");                          return 2;                      }                      else                      {                          int j=0;                          for(j=0;j<nums;j++)                          {                              if(fds[j]==-1)                              {                                  break;                              }                          }                          if(j==nums)                          {                              close(newsock);                          }                          else                          {                              fds[j]=newsock;//将新的所要关心的文件描述符对应的事件放到fds合适的位置                          }                      }                  }                else if(i!=0&&FD_ISSET(fds[i],&rds))//如果不是监听套接字但是是其他文件描述符对应的读事件就绪了                  {                      char buf[1024];                      ssize_t s=read(fds[i],buf,sizeof(buf)-1);                      if(s>0)                      {                          printf("client say:%s\n",buf);                      }                      else if(s==0)                      {                          printf("client quit!\n");                          close(fds[i]);                          fds[i]=-1;                      }                      else                      {                          perror("read");                          close(fds[i]);                          fds[i]=-1;                      }                  }              }      }    }    return 0;}

(1)每次调⽤用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 ’
(3)select⽀支持的⽂文件描述符数量太小了,默认是1024

原创粉丝点击