I/O多路转接---select

初始select

  • 系统提供select函数来实现多路复用输入/输出模型
  • select系统调用是用来监视多个文件描述符的状态变化的
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或者多个发生了变化.

select函数原型

  • selsect函数原型
  • #include<sys/select.h>
    int select(int nfds,fd_set *readfds,fd_set*writefds,fd_set*exceptfds,struct timerval*timeout)
    参数解释:

      a>nfds:需要监视的最大的文件描述符+1;

      b>readfds:可读事件文件描述符集合

      c>writefds:可写事件文件描述符集合

      d>exceptfds:异常事件文件描述符集合

           readfds writefds exceptfds都是输入输出型参数,输入时表示:要关注的文件描述符指定的事件,输出时表示,就绪的文件描述符状态

      e>timout:

        

              time>0:等待一段固定时间之后,select() 调用会返回,即使没有一个文件描述符处于I/O就绪状态

            timeout=0:非阻塞轮询方式,不断的去检测描述符集合的状态,然后立即返回
 
            timeout=NULL:阻塞的方式等待事件发生

            select()调用成功时,返回三个集合中I/O就绪的文件描述符总数;返回0代表在描述符词状态改变前已超过

timeout,没 有返回;出错返回-1


select函数原型中fd_set结构

  • fd_set是一个结构体,也就是一个数组,底层是一个位图,位图的每一位表示一个文件描述符的状态,输出时1表示需要监听的文件描述符,输入时1表示就绪的文件描述符.
  • 系统提供了一组操作位图的接口.
  • void FD_CLR(int fd,fd_set* set); //清除描述符词组set中对应的fd位
     
    int FD_ISSET(int fd,fd_set* set); //判断相应的fd位是否为真
     
    void FD_SET(int fd,fd_set* set);  //设置描述符词组set中对应的fd位
     
    void FD_ZERO(fd_set *set);  //清除描述符词组set的全部位

      

select函数的特点

  • 可监控的文件描述符个数取决于sizeof(fdset)的值.
  • 将fd加入select监控的同时,还需要使用一个数据结构array来保存放到select监控的fd

      一是因为select返回后,array作为元数据和fdset进行FDISSET判断

      二是因为select返回会把以前加入到但并无事件发生的fd清空,则每次开始select都要重新从array取fd逐一加入.

select函数的缺点(相对于poll和epoll而言)

  • 每次调用select,都需要手动的设置fd集合,从接口使用角度更方便.
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,开销大
  • 在内核需要遍历传递进来的fd,开销大
  • 支持的文件描述符太小

select函数优点(相对于多线程,多进程而言)

  • 可移植性好
  • 免去了系统调度的开销

select---检测标准输入输出

  1 #include <stdio.h>                                                                                                   
  2 #include <unistd.h>
  3 #include <sys/select.h>
  4 //系统提供select函数来实现多路转接输入输出模型
  5 //int select(int nfds,fd_set *readfds,fd_set * writefds,fd_set * exceptfds,struct timeval*timeout);
  6 //fd_set是一个结构体,也就是一个数组,更严格的说是一个"位图",用位图的每一位来标识文件描述符
  7 int main(){
  8     while(1){
  9   fd_set read_size;
 10   FD_ZERO(&read_size);//将底层的位图全部置0;
 11   FD_SET(0,&read_size);//将0号对应的文件描述符置1,
 12 //第一个NULL不关注哪些文件写就绪,
 13 //第二个NULL不关注哪些文件异常就绪,
 14 //第三个NULL,select阻塞形式来执行,
 15   int ret = select(1,&read_size,NULL,NULL,NULL);
 16   if(ret<0){
 17     perror("select");
 18     return 1;
 19   }
 20   //执行read的时候,就说明数据都已经准备 好了,直接进行读就可以了.
 21   char buf[1024] = {0};
 22   read(0,buf,sizeof(buf)-1);
 23   printf("%s\n",buf);
 24     }
 25 return 0;
 26 }            

select---网络服务器

  1 #include<stdio.h>                                                                                                    
  2 #include<string.h>
  3 #include<stdlib.h>
  4 #include<unistd.h>
  5 #include<sys/socket.h>
  6 #include<netinet/in.h>
  7 #include<arpa/inet.h>
  8 #include<sys/select.h>
  9 typedef struct sockaddr sockaddr;
 10 typedef struct sockaddr_in sockaddr_in;
 11 typedef struct FdSet{
 12   fd_set set;
 13   int max_fd;//set中最大的文件描述符
 14 }FdSet;
 15 int ProcessRequest(int new_sock){
 16 //循环从socket中读写数据
 17 char buf[1024] = {0};
 18 ssize_t read_size = read(new_sock,buf,sizeof(buf)-1);
 19 if(read_size<0){
 20  perror("read");
 21  return -1;
 22 }
 23 if(read_size==0){
 24   printf("read done!\n");
 25   return 0;
 26  }
 27 buf[read_size]  = '\0';
 28 printf("[client %d] say %s\n",new_sock,buf);
 29 write(new_sock,buf,strlen(buf));
 30 return 1;   
 31 }
 32 void FdSetInit(FdSet *fdset){
 33    FD_ZERO(&fdset->set);
 34    fdset->max_fd = 0;
 35 }                                                                                                                    
 36 void FdSetAdd(FdSet * fdset,int fd){
 37   FD_SET(fd,&fdset->set);//将位图中fd位置1;
 38   //维护位图中的最大文件描述符
 39   if(fdset->max_fd<fd){
 40     fdset->max_fd = fd;
 41   }
 42 }
 43 void FdSetDel(FdSet *fdset,int fd){
 44    FD_CLR(fd,&fdset->set);
 45    //删除有可能删掉最大的文件描述符,所以要时刻维护
 46    int max_fd = -1;
 47    int i=0;
 48    for(i=0;i<=fdset->max_fd;i++){
 49     if(!FD_ISSET(i,&fdset->set)){
 50        continue;
 51     }
 52     if(i>max_fd){
 53       max_fd = i;
 54     }                                                                                                                
 55    }
 56    fdset->max_fd = max_fd;
 57 }
 58 int Server_Init(char* ip,short port){
 59   //创建监听套接字
 60   int _listen_sock = socket(AF_INET,SOCK_STREAM,0);
 61   if(_listen_sock<0){
 62     perror("socket");
 63     return -1;
 64   }
 65   struct sockaddr_in server;
 66   server.sin_family = AF_INET;
 67   server.sin_addr.s_addr = inet_addr(ip);
 68   server.sin_port = htons(port);
 69   //需要对服务器进行绑定,使得客户端能够找到自己
 70  int ret = bind(_listen_sock,(struct sockaddr*)&server,sizeof(server));
 71   if(ret<0){
 72     perror("bind");
 73     return -1;                                                                                                       
 74   }
 75 
 76  if(listen(_listen_sock,5)){
 77      perror("listen");
 78      return -1;
 79  }
 80  return _listen_sock;
 81 }
 82 int main(int argc,char *argv[]){
 83    if(argc!=3){
 84      printf("usage: ./server [ip] [port]\n");
 85      return 1;
 86    }
 87    //初始化服务器,创建监听套接字,绑定监听
 88    int listen_sock = Server_Init(argv[1],atoi(argv[2]));
 89    if(listen_sock<0){
 90        printf("Server_Init failed\n");
 91        return 1;
 92    }                                                                                                                 
 93    printf("Server_Init ok!\n");
 94    //把listen_sock添加到对应的文件描述符位图中
 95    FdSet input_fds;
 96    FdSetInit(&input_fds);
 97    FdSetAdd(&input_fds,listen_sock);
 98    while(1){
 99    //这里需注意我们必须保证每次循环调用select的时候,传的参数必须包括listen_sock和new_sock,否则,如果listen_sock丢了
100    //就会导致,后续有任何客户端尝试和服务器建立连接,就都处理不了了.
101    //select监视多个文件描述符的变化
102    //所以这里我们用input_fds来保存select输入时的参数
103    //input_fds :添加内容 :有新的客户端链接进来
104    //output_fds:删除内容:客户端断开连接才删除
105    //用output_fds来保存select输出时的参数.
106    FdSet output_fds = input_fds;
107    int ret = select(output_fds.max_fd+1,&output_fds.set,NULL,NULL,NULL);
108    if(ret<0){
109      perror("select");
110      continue;
111     }                                                                                                                
112    if(FD_ISSET(listen_sock,&output_fds.set)){
113       //如果有新的客户端建立连接,那么listen_sock就在此位图中读就绪,此时就可以直接调用accept获取到这个链接
114       sockaddr_in client;
115       socklen_t len = sizeof(client);
116       int new_sock = accept(listen_sock,(sockaddr *)&client,&len);
117       if(new_sock<0){
118        perror("accept");
119        continue;
120       }
121       FdSetAdd(&input_fds,new_sock);
122       printf("[client %d]\n",new_sock);
123      }
124    //有可能是new_sock就绪也有可能是listen_sock就绪,以下处理new_sock就绪的情况
125      else{
126       //进行数据的读写
127      int i=0;
128      for(i=0;i<=output_fds.max_fd;i++){
129        if(!FD_ISSET(i,&output_fds.set)){
130            continue;                                                                                                 
131        }
132        //对当前就绪的进行处理
133      int ret =   ProcessRequest(i);
134      //当ret==0时说明客户端已经断开连接(即关闭了socket)
135      if(ret ==0){
136        close(i);
137        FdSetDel(&input_fds,i);
138      }
139      }
140      
141      }
142    }
143    return 0;
144 }              

            

猜你喜欢

转载自blog.csdn.net/sx2448826571/article/details/80615575
今日推荐