Linux TCP server系列(6)-select模式下的多线程server
目標:
? 修改上一篇的select模式下的server,讓它使用多線程來處理客戶端請求(多進程的模式已經在上篇中加了注釋)。
?
思路:
? (1)服務器
???????? ?我們已經在之前的客戶端模型多個并發用戶的過程中使用過多線程的技術了(其中還涉及到多線程利用條件變量進行線程同步),在這里我們可以很輕松的在上篇文章代碼中加入線程部分代碼。
//for thread
?????????????????? int *lptr;
?????????????????? pthread_t pid;
?????????????????? //for thread
?
?????????????????? for(i=0;i<=maxi;i++)
?????????????????? {
??????????????????????????? if((sockfd=client[i]) <0)
??????????????????????????? continue;
??????????????????????????? if(FD_ISSET(sockfd,&rset))
??????????????????????????? {
???????????????????????????????????? //thread client
???????????????????????????????????? lptr=(int*)malloc(sizeof(int));
???????????????????????????????????? *lptr=sockfd;
????????????????????????????????????
???????????????????????????????????? interrerr=pthread_create(&pid,NULL,threadPerClient,lptr);
???????????????????????????????????? if(errerr!=0)
???????????????????????????????????? printf("err%s",strerror(errno));
?
???????????????????????????????????? FD_CLR(sockfd,&allset);??????????????????????????????? //短連接后關閉socket,服務器發不過來
???????????????????????????????????? client[i]=-1;
?
???????????????????????????????????? printf("canread : %d,%d,%d/n",i,sockfd,nready);
???????????????????????????????????? if(--nready<=0)
???????????????????????????????????? break;
???????????????????????????????????? //threadclient
?
??????????????????????????? }
?????????????????? }
???????? 但是!!!!真的那么輕松就可以加入嗎?為什么我不直接在pthread_create中傳入sockfd,而是要再new一個整型數然后賦值給它再傳遞過去呢?
答案很明顯,如果直接使用sockfd的指針,那么在下次sockfd被client[i]賦值時,sockfd的值變了!而這時線程的實際操作的正是這個sockfd(不只因為線程共享變量,同時還因為傳遞的是指針)!所以我們復制一個新值,讓線程自己處理對應的fd。
這樣就完了?很明顯不會那么簡單,讓我們看看線程處理函數就知道。
void*threadPerClient(void *arg)
{
???????? int connfd=*((int*)arg);
???????? free(arg);???????????????????????????? //防止內存泄露
???????? pthread_detach(pthread_self());
?
???????? printf("client from %d(socketnum)/n",connfd);
???????? str_echo(connfd);
?
???????? close( connfd );
???????? return NULL;
}
看到注釋了嗎?“防止內存泄露“!!這是很容易發生的事,在剛才的代碼中new一個新對象,那么我們就必須在使用后釋放。除此以外我們還注意到這里調用了pthread_detach函數,因為線程分為joinable和unjoinable兩種。
如果線程是joinable狀態,當線程函數自己返回退出時或pthread_exit時都不會釋放線程所占用堆棧和線程描述符(總計8K多)。只有當你調用了pthread_join之后這些資源才會被釋放。若是unjoinable狀態的線程,這些資源在線程函數退出時或pthread_exit時自動會被釋放。
??? unjoinable屬性可以在pthread_create時指定,或在線程創建后在線程中pthread_detach自己, 如:pthread_detach(pthread_self()),將狀態改為unjoinable狀態,確保資源的釋放。或者將線程置為 joinable,然后適時調用pthread_join.
(其實就類似于進程退出時父親進程的wait)
(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<sys/time.h> 13 #include<pthread.h> 14 15 #define LISTEN_PORT 84 16 17 //服務器處理 18 void str_echo(int sockfd) // 服務器收到客戶端的消息后的響應 19 { 20 ssize_t n; 21 char line[512]; 22 23 printf("ready to read/n"); 24 25 while( (n=read(sockfd,line,512))>0 ) //阻塞IO版本 26 { 27 line[n]='/0'; 28 printf("Client Diary: %s/n",line); 29 30 char msgBack[512]; 31 snprintf(msgBack,sizeof(msgBack),"recv: %s/n",line); 32 write(sockfd,msgBack,strlen(msgBack)); 33 bzero(&line,sizeof(line)); 34 } 35 printf("end read/n"); 36 } 37 38 //子進程結束的信號處理 39 float timeuse; 40 void sig_child(int signo) //父進程對子進程結束的信號處理 41 { 42 pid_t pid; 43 int stat; 44 45 struct timeval tpstart,tpend; 46 //float timeuse; 47 gettimeofday(&tpstart,NULL); 48 49 while( (pid=waitpid(-1,&stat,WNOHANG))>0) 50 printf("child %d terminated/n",pid); 51 52 gettimeofday(&tpend,NULL); 53 54 timeuse+=1000000*(tpend.tv_sec-tpstart.tv_sec)+tpend.tv_usec-tpstart.tv_usec; 55 //timeuse/=1000000; 56 printf("Use Time:%f/n",timeuse/1000000); 57 return; 58 } 59 60 61 62 //使用線程代替進程來處理客戶請求 63 void* threadPerClient(void*arg) 64 { 65 int connfd=*((int*)arg); 66 free(arg); //防止內存泄露 67 pthread_detach(pthread_self()); 68 69 printf("client from %d(socket num)/n",connfd); 70 str_echo(connfd); 71 72 close( connfd ); 73 return NULL; 74 } 75 76 77 int main(int argc, char**argv) 78 { 79 int listenfd, connfd; 80 pid_t childpid; 81 socklen_t chilen; 82 83 struct sockaddr_in chiaddr,servaddr; 84 85 //for select 86 int i,maxi,maxfd,sockfd; 87 int nready,client[FD_SETSIZE]; 88 ssize_t n; 89 fd_set rset,allset; 90 //for select 91 92 listenfd=socket(AF_INET,SOCK_STREAM,0); 93 if(listenfd==-1) 94 { 95 printf("socket established error: %s/n",(char*)strerror(errno)); 96 } 97 98 bzero(&servaddr,sizeof(servaddr)); 99 servaddr.sin_family=AF_INET; 100 servaddr.sin_addr.s_addr=htonl(INADDR_ANY); 101 servaddr.sin_port=htons(LISTEN_PORT); 102 103 int bindc=bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); 104 if(bindc==-1) 105 { 106 printf("bind error: %s/n",strerror(errno)); 107 } 108 109 listen(listenfd,SOMAXCONN); //limit是SOMAXCONN 110 111 //initial "select" elements 112 maxfd=listenfd; //新增listenfd,所以更新當前的最大fd 113 maxi=-1; 114 for(i=0;i<FD_SETSIZE;i++) 115 client[i] =-1; 116 FD_ZERO(&allset); 117 FD_SET(listenfd,&allset); 118 //end initial 119 120 int cliconn=0; 121 signal(SIGCHLD,sig_child); 122 for(;;) 123 { 124 rset=allset; 125 nready=select(maxfd+1,&rset,NULL,NULL,NULL); //一開始select監聽的是監聽口 126 //如果有timeout設置,那么每次select之前都要再重新設置一下timeout的值 127 //因為select會修改timeout的值。 128 if(FD_ISSET(listenfd,&rset)) 129 { 130 chilen=sizeof(chiaddr); 131 132 connfd=accept(listenfd,(struct sockaddr*)&chiaddr,&chilen); 133 //阻塞在accept,直到三次握手成功了才返回 134 if(connfd==-1) 135 printf("accept client error: %s/n",strerror(errno)); 136 else 137 printf("client connected:%d/n",++cliconn); 138 139 for(i=0;i<FD_SETSIZE;i++) 140 { 141 if (client[i]<0) 142 { 143 client[i]=connfd; //找一個最小的插進入 144 break; 145 } 146 } 147 if(i==FD_SETSIZE) 148 { 149 printf("too many clients/n"); 150 exit(0); 151 } 152 FD_SET(connfd,&allset); //新加入的描述符,還沒判斷是否可以或者寫,所以后面使用rset而不是allset 153 154 155 156 if(connfd>maxfd) 157 maxfd=connfd; 158 if(i>maxi) 159 maxi=i; 160 if(--nready<=0) 161 continue; 162 } 163 164 //for thread 165 int*lptr; 166 pthread_t pid; 167 //for thread 168 169 for(i=0;i<=maxi;i++) 170 { 171 if( (sockfd=client[i]) <0) 172 continue; 173 if(FD_ISSET(sockfd,&rset)) 174 { 175 //thread client 176 lptr=(int*)malloc(sizeof(int)); 177 *lptr=sockfd; 178 //pthread_t pid; 179 180 int errerr=pthread_create(&pid,NULL,threadPerClient,lptr); 181 if(errerr!=0) 182 printf("err %s",strerror(errno)); 183 184 FD_CLR(sockfd,&allset); //短連接后關閉socket,服務器發不過來 185 client[i]=-1; 186 187 printf("can read : %d,%d,%d/n",i,sockfd,nready); 188 if(--nready<=0) 189 break; 190 //thread client 191 192 } 193 } 194 195 } 196 }?
作者: Aga.J
出處: http://www.cnblogs.com/aga-j
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
總結
以上是生活随笔為你收集整理的Linux TCP server系列(6)-select模式下的多线程server的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今天端午节 究竟是端午安康还是快乐?网友
- 下一篇: 关闭SQLite3中的journal暂存