Select的函数格式:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
先说明两个结构体:
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。
第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
具体解释select的参数:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加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值,表示不关心任何文件的写变化。
fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
返回值:返回状态发生变化的描述符总数。
负值:select错误
正值:某些文件可读写或出错
0:等待超时,没有可读写或错误的文件
缺点:
- 大并发数限制:使用32个整数的32位,即32*32=1024来标识fd,虽然可修改,但是有以下第二点的瓶颈;
- 每次调用select,都需要把fd集从用户态拷贝到内核态,这个开销在fd很多时会很大。
- 效率低:每次调用select都需要在内核遍历传递进来的所有fd.这个开销在fd很多时也会很大,每次都会线性扫描整个fd_set,集合越大速度越慢;
- 以下是我写的select 服务器:
#include<stdio.h> #include <sys/time.h> #include <sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/select.h> #include<stdlib.h> #include <unistd.h> int fds[sizeof(fd_set)*8]; static Usage(const char* proc) { printf("Usage:%s [local_ip],[local_port]\n",proc); } int start_up(const char* ip,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=inet_addr(ip); if(bind(sock,(struct addr*)&local,sizeof(local))<0){ perror("bind"); exit(3); } if(listen(sock,10)<0){ perror("listen"); exit(4); } return sock; } int main(int argc,char *argv[]) { if(argc!=3){ Usage(argv[0]); return 1; } int listen_sock=start_up(argv[1],atoi(argv[2])); printf("fd_set:%d\n",sizeof(fd_set)*8); int nums=sizeof(fds)/sizeof(fds[0]); int i=0; for(;i<nums;i++){ fds[i]=-1; } fds[0]=listen_sock; fd_set rfds; while(1){ int maxfd=-1; struct timeval timeout={0,0}; FD_ZERO(&rfds); i=0; for(;i<nums;i++){ if(fds[i]==-1){ continue; } FD_SET(fds[i],&rfds); if(maxfd<fds[i]){ maxfd=fds[i]; } } switch(select(maxfd+1,&rfds,NULL,NULL,NULL)){ case -1: printf("selsect\n"); break; case 0: printf("timeout...\n"); break; default://at least one fd ready { i=0; for(;i<nums;i++){ if(i==0&&FD_ISSET(fds[i],&rfds)){ struct sockaddr_in client; socklen_t len=sizeof(client); int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len); if(new_sock<0){ perror("accept"); continue; } printf("get a client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); int j=0; for(;j<nums;j++){ if(fds[j]==-1){ break; } } if(j==nums){ close(new_sock); }else{ fds[j]=new_sock; } }else if(i!=0&&FD_ISSET(fds[i],&rfds)){ //normal fd read event ready char buf[1024]; ssize_t s =read(fds[i],buf,sizeof(buf)-1); if(s>0){ buf[s]=0; printf("client#%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; } }else{ } } } break; } } return 0; }
输出结果: