select:
系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的对应的FILE *结构的表示就是stdin、stdout、stderr。
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;
rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。
struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位
参数timeout为结构timeval,用来设置select()的等待时间,
(1)如果参数timeout设为:
NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了
事件。
(2)0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
(3)特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
函数返回值:
执行成功则返回文件描述词状态已改变的个数
如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;
当有错误发生时则返回-1,
select实现I/0复用:
tcp_server.c:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<sys/types.h> 4 #include<sys/socket.h> 5 #include<arpa/inet.h> 6 #include<netinet/in.h> 7 #include<assert.h> 8 #include<unistd.h> 9 10 int fds[64]; 11 const int back_log=5; 12 void usage(char* argv) 13 { 14 printf("%s:[ip][port]\n",argv); 15 } 16 int start_up(char* ip,int port) 17 { 18 //sock 19 int sock=socket(AF_INET,SOCK_STREAM,0); 20 if(sock<0) 21 { 22 perror("sock"); 23 exit(0); 24 } 25 struct sockaddr_in local; 26 local.sin_port=htons(port); 27 local.sin_family=AF_INET; 28 local.sin_addr.s_addr=inet_addr(ip); 29 30 //bind 31 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) 32 { 33 perror("bind"); 34 exit(1); 35 } 36 //listen 37 if(listen(sock,back_log)<0) 38 { 39 perror("sock"); 40 exit(1); 41 } 42 return sock; 43 } 44 int main(int argc,char* argv[]) 45 { 46 if(argc!=3) 47 { 48 usage(argv[0]); 49 exit(1); 50 } 51 int port=atoi(argv[2]); 52 char* ip=argv[1]; 53 54 55 int done=0; 56 int new_sock=-1; 57 int listen_sock=start_up(ip,port); 58 struct sockaddr_in client; 59 socklen_t len=sizeof(client); 60 61 int max_fd; 62 fd_set _reads; 63 fd_set _writes; 64 65 int i=0; 66 int fds_num=sizeof(fds)/sizeof(fds[0]); 67 for(i=0;i<fds_num;i++) 68 { 69 fds[i]=-1; 70 } 71 fds[0]=listen_sock; 72 max_fd=fds[0]; 73 74 while(!done) 75 { 76 FD_ZERO(&_reads); //每次循环把_reads,_writes初始化(输入、输出 参数) 77 FD_ZERO(&_writes); 78 FD_SET(listen_sock,&_reads); //把listen_sock加到_reads文件描 述符集中 79 struct timeval _timeout={5,0}; //设置等待时间 80 for(i=0;i<fds_num;i++) 81 { 82 if(fds[i]>0) 83 { 84 FD_SET(fds[i],&_reads); 85 if(fds[i]>max_fd) 86 { 87 max_fd=fds[i]; 88 } 89 } 90 } 91 switch(select(max_fd+1,&_reads,&_writes,NULL,&_timeout)) //_reads,_writes输入,输出参数 92 { 93 case 0: 94 printf("timeout\n"); 95 break; 96 case -1: 97 perror("select"); 98 break; 99 default: 100 { 101 for(i=0;i<fds_num;i++) 102 { 103 if(fds[i]==listen_sock&&FD_ISSET(fds[i],&_reads)) //listen_sock 104 { 105 new_sock=accept(listen_sock,(struct sockaddr*)&clien t,&len); 106 107 if(new_sock<0) 108 { 109 perror("new_sock"); 110 continue; 111 } 112 printf("get connection...%ld\n",new_sock); 113 for(i=0;i<fds_num;i++) //把new_sock加到_reads文件描述集 114 { 115 if(fds[i]==-1) 116 { 117 fds[i]=new_sock; 118 break; 119 } 120 } 121 if(i==fds_num) //文件描述符个数已达到最大值 122 { 123 close(new_sock); 124 } 125 } 126 127 else if(fds[i]>0&&FD_ISSET(fds[i],&_reads)) //普通的sock,通信 128 { 129 char buf[1024]; 130 ssize_t _s=read(fds[i],buf,sizeof(buf)-1); 131 if(_s>0) 132 { 133 buf[_s]=‘\0‘; 134 printf("%s\n",buf); 135 } 136 else if(_s==0) 137 { 138 printf("client closed\n"); 139 } 140 else 141 { 142 perror("read"); 143 } 144 } 145 else 146 { 147 148 } 149 } 150 } 151 } 152 } 153 return 0; 154 }
tcp_client.c:
1 2 #include<sys/socket.h> 3 #include<sys/types.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<string.h> 7 #include<arpa/inet.h> 8 #include<netinet/in.h> 9 #include<string.h> 10 #include<stdlib.h> 11 #include<stdio.h> 12 13 14 void usage(char* proc) 15 { 16 printf("Usage:%s[ip][port]\n",proc); 17 } 18 int main(int argc,char* argv[]) 19 { 20 if(argc!=3) 21 { 22 usage(argv[0]); 23 exit(1); 24 } 25 char* ip=argv[1]; 26 int port=atoi(argv[2]); 27 28 //socket 29 int sock=socket(AF_INET,SOCK_STREAM,0); 30 if(sock<0) 31 { 32 perror("sock"); 33 exit(2); 34 } 35 struct sockaddr_in remote; 36 remote.sin_family=AF_INET; 37 remote.sin_port=htons(port); 38 remote.sin_addr.s_addr=inet_addr(ip); 39 40 int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote)); 41 if(ret<0) 42 { 43 perror("coneect"); 44 } 45 46 char buf[1024]; 47 while(1) 48 { 49 memset(buf,‘\0‘,sizeof(buf)); 50 read(0,buf,sizeof(buf)-1); 51 ssize_t _s= write(sock,buf,sizeof(buf)-1); 52 if(_s<0) 53 { 54 perror("write"); 55 } 56 } 57 return 0; 58 }
结果:
server端:
[admin@www Internet1]$ ./tcp_server 127.0.0.1 8080 get connection...4 timeout we are young ^C
client端:
[admin@www Internet1]$ ./tcp_client 127.0.0.1 8080 we are young ^C [admin@www Internet1]$
select缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
本文出自 “liveyoung” 博客,转载请与作者联系!
原文地址:http://youngyoungla.blog.51cto.com/10697042/1783663