十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
多路复用之select
静宁网站制作公司哪家好,找成都创新互联!从网页设计、网站建设、微信开发、APP开发、成都响应式网站建设公司等网站项目制作,到程序开发,运营维护。成都创新互联2013年至今到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选成都创新互联。
之前在套接字编程中我们用了多线程和多进程的方法来编写,用它们编写的好处自然是稳定,而却非常耗资源,在前面的高级I/O博客中说到了另外一种方式那便是高效的多路复用方式了,它会一次等待多个满足条件的文件描述符,这里先介绍第一种多路复用方式select后面还会介绍比它更好的epoll和最好的多路复用方式epoll。
select的多路复用方式是这样的:它需要一个buf来存放需要监听的文件描述符,先要把所关心的文件描述符放入到所对应的读事件集合或者写事件集合中,一旦其中发生变化会以参数反应出来。
先来看看select的函数
/* According to POSIX.1-2001 */ #include/* According to earlier standards */ #include #include #include int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
nfds:是所要监控的文件描述符的个数加一,注意,一定要加一!!!
fd_set *readfds:表示监控的readfds集,这个fd_set是有上限的,我的机子上是128,那么表示最多能监控128*8=1024个。readfds是输入输出型的参数,它的每个bit为会对应监控一个文件描述符,select是阻塞式的等待事件的发生的,当某个文件描述符就绪则把它对应的bit为置1,而其它的都要清空,所以每次在select之前都要先执行emty(下面会说到),和set(下面会说到)。
writeds:写事件文件描述符集
exceptfds:错误事件文件描述符集
timeout:设置等待事件,等待事件到了可以做其他的事情,比如提示。。
FD_CLR清空所设置关心的文件描述符集
FD_ISSET:用于检查该文件描述符是否在对应集合中
FD_SET:用于将所关心的文件描述符添加入对应集合中
FD_ZERO:将对应文件描述符集中的所有bit位置0
下面是我对select的工作流程理解
下面是一个使用select实现的TCPserver的简单代码
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 11 int fd[64]; //select需要创建一个用于存放socket文件描述符的buf 12 void usage(char *proc) 13 { 14 printf("%s+[ip]+[port]\n",proc); 15 } 16 17 int listen_sock(const char*ip,int port) 18 //创建listen套接字前面的socket博客中已经详细说明了 19 { 20 int sock=socket(AF_INET,SOCK_STREAM,0); 21 if(sock<0) 22 { 23 perror("socket"); 24 } 25 struct sockaddr_in server; 26 server.sin_family=AF_INET; 27 server.sin_addr.s_addr=inet_addr(ip); 28 server.sin_port=htons(port); 29 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0) 30 { 31 32 perror("bind"); 33 } 34 if(listen(sock,10)<0) 35 { 36 perror("listen"); 37 } 38 return sock; 39 } 40 41 void select_server(int sock) 42 { 43 int i=0; 44 for(;i<64;i++) //初始化为无效的文件描述符 45 { 46 fd[i]=-1; 47 } 48 fd_set reads; //定义读文件描述符集 49 printf("%d\n",sizeof(fd_set)); 50 fd_set writes; //定义写文件描述符集 51 int max_fd; //定义select所需要的参数,比最大的文件描述符大1 52 int newsock=-1;//定义所需要的accept到的文件描述符 53 int fd_len=sizeof(fd)/sizeof(fd[0]);//select参数 54 55 struct sockaddr_in routom; 56 socklen_t routom_len=sizeof(routom); 57 58 struct timeval timeout;//设置最大等待时间用于提醒 59 fd[0]=sock; //将buff中的第一个设置为监听套接字用于监听 60 max_fd=sock; //目前sock是最大的文件描述符 61 62 char buf[1024]; 63 while(1) 64 { 65 timeout.tv_sec=5; //单位为秒 66 timeout.tv_usec=0; //单位为毫秒 67 FD_ZERO(&reads); //初始化 68 FD_ZERO(&writes); 69 FD_SET(sock,&reads); 70 //将sock添加到reads读文件描述符集中因为每次我们都需要这个文件描述符 71 int i=0; 72 for(;i 服务器显示,所以我们不关心写文件描述符,只有两种我们 76 //需要关心的文件描述符1.accept到的需要链接服务器的文件描述符.2.有数据 77 //需要去读的问家描述符。 78 if(fd[i]>0) 79 { 80 FD_SET(fd[i],&reads); 81 if(fd[i]>max_fd) 82 { 83 max_fd=fd[i]; //将最大的文件描述符给max_fd 84 } 85 } 86 } 87 switch(select(max_fd+1,&reads,&writes,NULL,&timeout)){ 88 //正式进入select函数 89 case -1: 90 perror("select"); 91 case 0 : //返回值为0代表timeout了 92 printf("timeout...\n"); 93 break; 94 default: 95 { 96 i=0; 97 for(;i 0&&FD_ISSET(fd[i],&reads)) 130 //满足有数据要读的文件描述符 131 { 132 memset(buf,'\0',sizeof(buf)); 133 ssize_t size=read(fd[i],buf,sizeof(buf)-1); 134 if(size>0) 135 { 136 buf[size]='\0'; 137 printf("[%s]::%s\n",inet_ntoa\ 138 (routom.sin_addr),buf); 139 }else if(size==0){ 140 //size小于零时,则说明远端已经关闭 141 printf("[ip]%s:is shutdown\n",\ 142 inet_ntoa(routom.sin_addr)); 143 close(fd[i]); 144 fd[i]=-1; 145 }else{ 146 ; 147 } 148 } 149 } 150 } 151 } 152 } 153 } 154 155 int main(int argc,char *argv[]) 156 { 157 if(argc!=3) 158 { 159 usage(argv[0]); 160 } 161 char *ip=argv[1]; 162 int port=atoi(argv[2]); 163 int sock=listen_sock(ip,port); 164 165 select_server(sock); 166 close(sock); 167 return 0; 168 }
总结:select可以一次监听多个事件,但是它的缺点是很明显的。
1、它是有监听上限的,我的电脑上限是1024个。
2、它是通过输出型参数来返回的,这样我们就不得不每次监听到都要遍历 一次整个存放文件描述符的buf。
3、它会在设置的时候在用户态和内核态中来回copy这样开销很大。