系统提供select函数来实现多路复用输入/输出模型。
select函数让我们的程序监视多个文件描述符的状态变化。程序会停在select这里等待,直到被监视的文件描述符中有一个或多个发生了状态变化
函数原型如下:
返回值: 成功返回就绪描述符的个数,超过timeout时间且没有任何事件发生返回0,失败返回-1
参数解释:
nfds: 被监视的文件描述符中值最大描述符值加1(描述符是从0开始的,描述符0、1、2...nfds-1均将被测试)
下面三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述符。如果对某一个的条件不感兴趣,就可以把它设为空指针。
readfds: 读描述符集(所有关心读事件的描述符被用户添加进来),这个参数是个输入输出型参数
writefds: 写描述符集
exceptfds: 异常描述符集
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
void FD_CLR(int fd, fd_set *fdset); //把给定的文件描述符从指定集合删除
int FD_ISSET(int fd, fd_set *fdset); //判断指定描述符时候被加入了指定集合中(是否可读/可写)
void FD_SET(int fd, fd_set *fdset); //把一个给定的文件描述符加入集合之中
void FD_ZERO(fd_set *fdset); //清空集合
timeout: 设置一段时间
(1)timeout=NULL时,当前进程一直等待,直到有一个描述符准备好I/O(永远等下去)
(2)timeout指向timeval结构体且时间设置不为0时,当前进程挂起,如果到了设定的时间(timeout)还没有任何事件发生就返回0,进程继续执行后面的代码(等待一段固定时间)
(3)timeout指向timeval结构体且时间设置为0时,不会等待,立即返回(根本不等待)
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
server_select.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define BLOCK 6 int fds[64]; void usage(char * proc) { printf("%s [ip] [port]\n",proc); } int create_sock(char *port,const char * inaddr) { //1. int listenfd=socket(AF_INET,SOCK_STREAM,0); if(listenfd<-1){ perror("listenfd"); exit(1); } //2. struct sockaddr_in local; local.sin_family=AF_INET; int _port=atoi(port); local.sin_port=htons(_port); local.sin_addr.s_addr=inet_addr(inaddr); struct linger lig; int iLen; lig.l_onoff=1; lig.l_linger=0; iLen=sizeof(struct linger); setsockopt(listenfd,SOL_SOCKET,SO_LINGER,(char *)&lig,iLen); //3. if(bind(listenfd,(struct sockaddr*)&local,sizeof(local))<0){ perror("bind"); exit(2); } //4. if(listen(listenfd,BLOCK)<0){ perror("listen"); exit(3); } return listenfd; } int main(int argc,char* argv[]) { if(argc!=3){ usage(argv[0]); exit(1); } //create listenfd int listen_fd=create_sock(argv[2],argv[1]); struct sockaddr_in client; socklen_t len=sizeof(client); int max_fd=listen_fd; char buf[1024]; //initialize fds[] int fds_num=sizeof(fds)/sizeof(fds[0]); int i=0; for(i=0;i<fds_num;++i){ fds[i]=-1; } fds[0]=listen_fd; int done=0; while(!done){ //define and initialize parameter of select() fd_set readset; FD_ZERO(&readset); FD_SET(listen_fd,&readset); struct timeval timeout; timeout.tv_sec=5; timeout.tv_usec=0; //重新添加描述符到readset中 for(i=1;i<fds_num;++i){ if(fds[i]>0){ FD_SET(fds[i],&readset); if(fds[i]>max_fd) max_fd=fds[i]; } else break; } switch(select(max_fd+1,&readset,NULL,NULL,&timeout)){ case -1: perror("select"); break; case 0: printf("timeout...\n"); break; default: //遍历所有的描述符 for(i=0;i<fds_num;++i){ //listen_fd happen if(fds[i]==listen_fd && FD_ISSET(fds[i],&readset)){ int connfd=accept(listen_fd,(struct sockaddr*)&client,&len); if(connfd<0){ perror("accept"); break; }else{ printf("get a connect...\n"); for(i=0;i<fds_num;++i){ if(fds[i]==-1){ fds[i]=connfd; FD_SET(connfd,&readset); break; } } } } //normal event happen else if(fds[i]>0 && FD_ISSET(fds[i],&readset)){ ssize_t _size=read(fds[i],buf,sizeof(buf)-1); if(_size<0){ perror("read"); }else if(_size==0){//client closed printf("client shutdown...\n"); close(fds[i]); fds[i]=-1; continue; }else{ buf[_size]=‘\0‘; printf("client# %s",buf); while(write(fds[i],buf,sizeof(buf)-1)<0){ perror("write"); } } } else{ } } break; } } return 0; }
client_select.c
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> #include <netinet/in.h> void usage(char *proc) { printf("%s [ip] [port]\n",proc); } int creat_socket() { int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0){ perror("socket"); exit(1); } return fd; } int main(int argc,char* argv[]) { if(argc!=3){ usage(argv[0]); exit(1); } int fd=creat_socket(); int _port=atoi(argv[2]); struct sockaddr_in addr; addr.sin_family=AF_INET; addr.sin_port=htons(_port); inet_aton(argv[1],&addr.sin_addr); socklen_t addrlen=sizeof(addr); if(connect(fd,(struct sockaddr*)&addr,addrlen)<0){ perror("connect"); exit(2); } char buf[1024]; while(1){ memset(buf,‘\0‘,sizeof(buf)); printf("Please Enter:"); fgets(buf,sizeof(buf)-1,stdin); if(send(fd,buf,sizeof(buf)-1,0)<0){ perror("send"); continue; } //回显 ssize_t _size=recv(fd,buf,sizeof(buf)-1,0); if(_size>0){ buf[_size]=‘\0‘; printf("echo->%s\n",buf); } } return 0; }
运行结果:(可把服务器端和客户端结果对比起来看)
服务器端
客户端
select模型的缺点:
1.我的机器测试
printf("fd_set=%d\n",sizeof(fd_set)); 值为128,说明能监视的描述符的最大值为128*8=1024个
2.每次都要把所关心的描述符重新添加一次,即从用户态拷贝到内核态(因为select返回时没有发生事件的描述符将被清空),如果描述符较多的时候,开销是很大的
3.当select返回的时候,即使只有一个事件发生了,也要把整个集遍历一次,同样,如果描述符较多的时候,开销也很大