IO多路转接之select

系统提供select函数来实现多路转接。

调用select函数接口的特点:一次需要等待多个文件描述符。

select函数原型:

#include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>

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

nfds:要关心的最大文件描述符+1,限定了文件描述符的遍历区间。

一个文件描述符需要关心三个事件:

  1. 读事件
  2. 写事件
  3. 异常事件

fd_set实际上是一个位图

timeval结构体


timeout:表示select可以每隔多久时间醒过来等待一次,如果所期望的时间没有发生,就去忙别的。

使用select编写简单的服务器:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<arpa/inet.h>
#define MAX_FD sizeof(fd_set)*8//最多可监听多少个文件描述符
#define INIT -1
int starup(int port){
  int sock=socket(AF_INET,SOCK_STREAM,0);
  if(sock<0){
    perror("socket");
    exit(2);
  }
  int opt=1;
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

  struct sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port=htons(port);
  local.sin_addr.s_addr=htonl(INADDR_ANY);
  if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
    perror("bind");
    exit(3);
  }
  if(listen(sock,5)<0){
    perror("listen_sock");
    exit(4);
  }
  return sock;
}
void array_init(int* fd_array,int num){
  int i=0;
  for(;i<num;i++){
    fd_array[i]=INIT;
  }
}
int array_add(int *fd_array,int num,int fd){
  int i=0;
  for(;i<num;i++){
    if(fd_array[i]==INIT){
      fd_array[i]=fd;
      return 0;
    }
  }
  return -1;
}
int  set_rfds(int* fd_array,int num,fd_set* rfds){
  int i=0;
  int max_fd=INIT;
  for(;i<num;i++){
    if(fd_array[i]>INIT){
      FD_SET(fd_array[i],rfds);
      if(max_fd<fd_array[i]){
        max_fd=fd_array[i];
      }
    }
  }
  return max_fd;
}
void array_del(int* fd_array,int num,int index){
  if(index<num&&index>0){
    fd_array[index]=INIT;
  }
}

void  service(int* fd_array,int num,fd_set* rfds){
  int i=0;
  for(;i<num;i++){
	  //判断数组中的文件描述符是否存在在对应位图位置上
    if(fd_array[i]>INIT&&FD_ISSET(fd_array[i],rfds)){
      int fd=fd_array[i];
      if(i==0){
        struct sockaddr_in client;
        socklen_t len=sizeof(client);
		//建立连接
        int new_sock=accept(fd,(struct sockaddr*)&client,&len);
        if(new_sock<0){
          perror("accept");
          continue;
        }
        if(array_add(fd_array,num,new_sock)<0){
          printf("server is busy!\n");
          close(new_sock);
        }
      }else{
        char buf[1024]={0};
        ssize_t s=read(fd,buf,sizeof(buf)-1);
        if(s>0){
          buf[s]=0;
          printf("client:>%s\n",buf);
        }else if (s==0){
          close(fd);
          array_del(fd_array,num,i);
        }else{
          perror("read");
          close(fd);
          array_del(fd_array,num,i);
        }
      }
    }
  }
}


int main(int argc,char* argv[]){
  if(argc!=2){
    printf("usage :%s [port]\n",argv[0]);
    return 1;
  }
  //创建监听套接字
  int listen_sock=starup(atoi(argv[1]));
  int fd_array[MAX_FD];
  //初始化数组
  array_init(fd_array,MAX_FD);
  //将监听到的新连接加入到数组里
  array_add(fd_array,MAX_FD,listen_sock);
  
  fd_set rfds;//位图
  int max_fd=0;
  for(;;){
    struct timeval timeout={5,0};
    //清空位图
	FD_ZERO(&rfds);
	//将数组中的文件描述符添加到位图中
    max_fd=set_rfds(fd_array,MAX_FD,&rfds);
    switch(select(max_fd+1,&rfds,NULL,NULL,&timeout)){
      case 0:
        printf("select timeout....\n");
        break;
      case -1:
        perror("select");
        break;
      default:
        service(fd_array,MAX_FD,&rfds);//select>0说明建立好连接,现在需要服务器提供服务
        break;
    }

  }
}

select缺点:

每次调用select,都要手动设置fd集合。

select限定了可接收的文件描述符上限。

猜你喜欢

转载自blog.csdn.net/Moralin_/article/details/80680020
今日推荐