系统提供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,限定了文件描述符的遍历区间。
一个文件描述符需要关心三个事件:
- 读事件
- 写事件
- 异常事件
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限定了可接收的文件描述符上限。