目标:
让服务器退化为单进程模式,但是利用select来提升性能
思路:
(1)服务器
传统的单进程服务器一旦accept了客户端的TCP连接后,就转入客户请求的处理,处理完成后才能再一次的调用accept来接受下一个客户端的TCP连接和请求。
为了更加提高单进程server的性能,本程序使用select这种IO复用的模式,同时监听已经连接的socket端口和正在监听的服务器listening端口,这样一来,就可以大大提升sever处理并发请求的能力。
select的使用方式如下:
a)定义fd_set
fd_set allset;
select允许我们监听来自标准输入,标准输出,标准错误输出的IO信号,本例中我们监听标准输入IO信号集
b)注册将要被监听的fd
FD_SET( listenfd, &allset )
通过FD_SET和 FD_CLR可以注册和清除某个fd_set内的fd项,使得在调用select的时候可以监听或者取消监听某个fd
c)如果IO信号到达,识别并处理
通过FD_ISSET可以判断select所监听的fd_set上的IO是否有状态变化,一旦返回true,则可以对该fd进行操作。
select使用事项及技巧:
a)使用select时应该注意,如果select有timeout设置,那么每次select之前都要再重新设置一下timeout的值,因为select成功的话会修改timeout的值。
b)本例中,如果我们在某次select中捕获到listenfd的IO状态有变,也就是说有新的客户端连接,我们不会马上做客户端的请求处理,而是把连接到的socket fd插入到select的监听集合中,然后继续探测其他监听集有IO状态变化(这里的其他监听集就是每个已经连接的客户端的socket fd的状态),如果有变化则马上处理client的请求。这样做的好处是我们及时处理了已连接的客户端的请求,而不是被新连接的客户端的请求所抢占,反正旧客户端被饿死的情况发生。
c) 本例是在单进程服务器上使用select,所以适合简单客户请求处理,也就是短连接的情况,如果需要长时间服务于多个客户,可以使用fork加以辅助
(2)客户端无需改动
代码:
server.cpp1 #include<sys/types.h> 2 #include<sys/socket.h> 3 #include<strings.h> 4 #include<arpa/inet.h> 5 #include<unistd.h> 6 #include<stdlib.h> 7 #include<stdio.h> 8 #include<string.h> 9 #include<errno.h> 10 #include<signal.h> 11 #include<sys/wait.h> 12 #include<pthread.h> 13 14 #define LISTEN_PORT 84 15 16 void str_echo(int sockfd) // 服务器收到客户端的消息后的响应 17 { 18 ssize_t n; 19 char line[512]; 20 21 printf("ready to read/n"); 22 23 while( (n=read(sockfd,line,512))>0 ) 24 { 25 line[n]='/0'; 26 printf("Client Diary: %s/n",line); 27 28 char msgBack[512]; 29 snprintf(msgBack,sizeof(msgBack),"recv: %s/n",line); 30 write(sockfd,msgBack,strlen(msgBack)); 31 bzero(&line,sizeof(line)); 32 } 33 34 printf("end read/n"); 35 36 } 37 38 void sig_child(int signo) //父进程对子进程结束的信号处理 39 { 40 pid_t pid; 41 int stat; 42 43 while( (pid=waitpid(-1,&stat,WNOHANG))>0) 44 printf("child %d terminated/n",pid); 45 46 return; 47 } 48 49 50 int main(int argc, char **argv) 51 { 52 53 int listenfd, connfd; 54 pid_t childpid; 55 socklen_t chilen; 56 57 struct sockaddr_in chiaddr,servaddr; 58 59 //values for select 60 int i,maxi,maxfd,sockfd; 61 int nready,client[FD_SETSIZE]; 62 ssize_t n; 63 fd_set rset,allset; 64 //values for select 65 66 listenfd=socket(AF_INET,SOCK_STREAM,0); 67 if(listenfd==-1) 68 { 69 printf("socket established error: %s/n",(char*)strerror(errno)); 70 } 71 72 bzero(&servaddr,sizeof(servaddr)); 73 servaddr.sin_family=AF_INET; 74 servaddr.sin_addr.s_addr=htonl(INADDR_ANY); 75 servaddr.sin_port=htons(LISTEN_PORT); 76 77 int bindc=bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); 78 if(bindc==-1) 79 { 80 printf("bind error: %s/n",strerror(errno)); 81 } 82 83 listen(listenfd,SOMAXCONN); //limit是SOMAXCONN 84 85 //initial "select" elements 86 maxfd=listenfd; //新增listenfd,所以更新当前的最大fd 87 maxi=-1; 88 for(i=0;i<FD_SETSIZE;i++) 89 client[i] = -1; 90 FD_ZERO(&allset); 91 FD_SET(listenfd,&allset); 92 //initial "select" elements 93 94 signal(SIGCHLD,sig_child); 95 for(;;) 96 { 97 rset=allset; //rset和allset的搭配使得新加入的fd要等到下次select才会被监听 98 nready=select(maxfd+1,&rset,NULL,NULL,NULL); //一开始select监听的是监听口 99 //如果有timeout设置,那么每次select之前都要再重新设置一下timeout的值 100 //因为select会修改timeout的值。 101 102 if(FD_ISSET(listenfd,&rset)) 103 { 104 chilen=sizeof(chiaddr); 105 106 connfd=accept(listenfd,(struct sockaddr*)&chiaddr,&chilen); 107 //阻塞在accept,直到三次握手成功了才返回 108 if(connfd==-1) 109 printf("accept client error: %s/n",strerror(errno)); 110 else 111 printf("client connected/n"); 112 113 for(i=0;i<FD_SETSIZE;i++) 114 { 115 if (client[i]<0) 116 { 117 client[i]=connfd; //找一个最小的插进入,并且缓存在client中,这样就不需要遍历所有fd,包括为0位的,来查看是否ISSET 118 break; 119 } 120 } 121 if(i==FD_SETSIZE) 122 { 123 printf("too many clients/n"); 124 exit(0); 125 } 126 FD_SET(connfd,&allset); //新加入的描述符,还没判断是否可以或者写,所以后面使用rset而不是allset 127 128 if(connfd>maxfd) //maxfd是为了下次select,作为参数使用 129 maxfd=connfd; 130 if(i>maxi) //maxi是为了减少遍历所监听fd的次数 131 maxi=i; 132 if(--nready<=0) //nready用来辅助计数,这样就不要遍历整个client数组 133 continue; 134 } 135 136 137 for(i=0;i<=maxi;i++) 138 { 139 if( (sockfd=client[i]) <0) 140 continue; 141 if(FD_ISSET(sockfd,&rset)) 142 { 143 //单进程的环境下,不可以阻塞在这里,可以选择非阻塞,线程,超时.也就无法防范拒绝服务的攻击 144 //比较适合短连接的情况 145 146 //单进程不使用fork的情况! 147 //test fork 148 // if((childpid=fork())==0) 149 { 150 close(listenfd); 151 printf("client from %s/n",inet_ntoa(chiaddr.sin_addr)); 152 str_echo(connfd); 153 close(connfd); 154 155 exit(0); 156 } 157 // else if (childpid<0) 158 // printf("fork error: %s/n",strerror(errno)); 159 close(connfd); 160 //test fork 161 162 FD_CLR(sockfd,&allset); //清除,表示已被处理 163 client[i]=-1; 164 165 printf("can read : %d,%d,%d/n",i,sockfd,nready); 166 if(--nready<=0) //nready用来辅助计数,这样就不要遍历整个client数组 167 break; 168 } 169 } 170 } 171 }
作者: Aga.J
出处: http://www.cnblogs.com/aga-j
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。