Linux TCP server系列(5)-select模式下的单进程server
目標(biāo):
?讓服務(wù)器退化為單進(jìn)程模式,但是利用select來提升性能
?
思路:
??? (1)服務(wù)器
?????? 傳統(tǒng)的單進(jìn)程服務(wù)器一旦accept了客戶端的TCP連接后,就轉(zhuǎn)入客戶請求的處理,處理完成后才能再一次的調(diào)用accept來接受下一個客戶端的TCP連接和請求。
?????? 為了更加提高單進(jìn)程server的性能,本程序使用select這種IO復(fù)用的模式,同時監(jiān)聽已經(jīng)連接的socket端口和正在監(jiān)聽的服務(wù)器listening端口,這樣一來,就可以大大提升sever處理并發(fā)請求的能力。
????? select的使用方式如下:
????? a)定義fd_set
???????? fd_set allset;
???????? select允許我們監(jiān)聽來自標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯誤輸出的IO信號,本例中我們監(jiān)聽標(biāo)準(zhǔn)輸入IO信號集
????? b)注冊將要被監(jiān)聽的fd
?????? ?FD_SET(? listenfd, &allset )
??????? 通過FD_SET和 FD_CLR可以注冊和清除某個fd_set內(nèi)的fd項,使得在調(diào)用select的時候可以監(jiān)聽或者取消監(jiān)聽某個fd
???? c)如果IO信號到達(dá),識別并處理
??????? 通過FD_ISSET可以判斷select所監(jiān)聽的fd_set上的IO是否有狀態(tài)變化,一旦返回true,則可以對該fd進(jìn)行操作。
?
???? select使用事項及技巧:
?????? a)使用select時應(yīng)該注意,如果select有timeout設(shè)置,那么每次select之前都要再重新設(shè)置一下timeout的值,因為select成功的話會修改timeout的值。
?????? b)本例中,如果我們在某次select中捕獲到listenfd的IO狀態(tài)有變,也就是說有新的客戶端連接,我們不會馬上做客戶端的請求處理,而是把連接到的socket fd插入到select的監(jiān)聽集合中,然后繼續(xù)探測其他監(jiān)聽集有IO狀態(tài)變化(這里的其他監(jiān)聽集就是每個已經(jīng)連接的客戶端的socket fd的狀態(tài)),如果有變化則馬上處理client的請求。這樣做的好處是我們及時處理了已連接的客戶端的請求,而不是被新連接的客戶端的請求所搶占,反正舊客戶端被餓死的情況發(fā)生。
?????? c) 本例是在單進(jìn)程服務(wù)器上使用select,所以適合簡單客戶請求處理,也就是短連接的情況,如果需要長時間服務(wù)于多個客戶,可以使用fork加以輔助
?(2)客戶端無需改動
代碼:
server.cpp 1 #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) // 服務(wù)器收到客戶端的消息后的響應(yīng) 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) //父進(jìn)程對子進(jìn)程結(jié)束的信號處理 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,所以更新當(dāng)前的最大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才會被監(jiān)聽 98 nready=select(maxfd+1,&rset,NULL,NULL,NULL); //一開始select監(jiān)聽的是監(jiān)聽口 99 //如果有timeout設(shè)置,那么每次select之前都要再重新設(shè)置一下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; //找一個最小的插進(jìn)入,并且緩存在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,作為參數(shù)使用 129 maxfd=connfd; 130 if(i>maxi) //maxi是為了減少遍歷所監(jiān)聽fd的次數(shù) 131 maxi=i; 132 if(--nready<=0) //nready用來輔助計數(shù),這樣就不要遍歷整個client數(shù)組 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 //單進(jìn)程的環(huán)境下,不可以阻塞在這里,可以選擇非阻塞,線程,超時.也就無法防范拒絕服務(wù)的攻擊 144 //比較適合短連接的情況 145 146 //單進(jìn)程不使用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用來輔助計數(shù),這樣就不要遍歷整個client數(shù)組 167 break; 168 } 169 } 170 } 171 }
作者: Aga.J
出處: http://www.cnblogs.com/aga-j
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
總結(jié)
以上是生活随笔為你收集整理的Linux TCP server系列(5)-select模式下的单进程server的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 替代谷歌 苹果自研搜索引擎曝光
- 下一篇: 浙商银行VISA标准卡分期手续费怎么算