服務器線程並發和進程並發


進程和線程的使用在前面博文已經講述完畢,在完成一個最簡單的服務器之后,就是要考慮下如何實現並發服務器了。

要實現服務的並發,只能通過進程和線程兩種方式。

之前提到過listen_fd和connect_fd,listen用於監聽是否有客戶端連接,維護兩個fd隊列,沒完成握手的和完成就緒的。

connect從就緒隊列取描述符,這個connect_fd描述符將用於數據通信,所以要實現並發,就是將connect_fd分發到線程或進程上,由他們去獨立完成通信。

在實際並發服務器應用場合,在IO層大多通過兩個地方來提高代碼效率,一個是描述符處理,一個是線程/進程調度處理。

下圖簡單描述了並發服務器的原理:

image

在處理IO時,會用到IO復用技術提高效率,在線程/進程分配時,會先構造線程池或進程池,並以某種方式調度,這些在后續博文詳細描述。

下面是並發實現的簡單代碼,利用線程和進程實現服務器的並發。

進程實現:

  1 /* File Name: server.c */  
  2 #include <stdio.h>  
  3 #include <stdlib.h>  
  4 #include <string.h>  
  5 #include <unistd.h>
  6 #include <sys/wait.h>
  7 #include <errno.h>  
  8 #include <sys/types.h>  
  9 #include <sys/socket.h>  
 10 #include <sys/unistd.h>
 11 #include <netinet/in.h>  
 12 
 13 const int DEFAULT_PORT = 2500;  
 14 const int BUFFSIZE = 1024;
 15 const int MAXLINK = 10;
 16 
 17 class sigOp
 18 {
 19 public:
 20      void addSigProcess(int sig,void (*func)(int));
 21     void sendSig(const int sig, const int pid);
 22 };
 23 
 24 void sigOp::addSigProcess(int sig,void (*func)(int))
 25 {
 26     struct sigaction stuSig;
 27     memset(&stuSig, '\0', sizeof(stuSig));
 28     stuSig.sa_handler = func;
 29     stuSig.sa_flags |= SA_RESTART;
 30     sigfillset(&stuSig.sa_mask);
 31     sigaction(sig, &stuSig, NULL);
 32 }
 33 
 34 void sigOp::sendSig(const int sig, const int pid)
 35 {
 36     kill(pid, sig);
 37 }
 38 
 39 void waitchlid(int sig)
 40 {
 41     pid_t pid;
 42     int stat;
 43     while((pid = waitpid(-1, &stat, WNOHANG)) > 0);
 44 }
 45 
 46 int main(int argc, char** argv)  
 47 {  
 48     int    socket_fd, connect_fd;  
 49     struct sockaddr_in servaddr;  
 50     char buff[BUFFSIZE];    
 51     
 52     if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
 53     {  
 54         printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
 55         exit(0);  
 56     } 
 57     
 58     memset(&servaddr, 0, sizeof(servaddr));  
 59     servaddr.sin_family = AF_INET;  
 60     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 61     servaddr.sin_port = htons(DEFAULT_PORT);
 62   
 63     if (bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
 64     {  
 65         printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
 66         exit(0);  
 67     }  
 68     
 69     if (listen(socket_fd, MAXLINK) == -1)
 70     {  
 71         exit(0);  
 72     }   
 73 
 74     sigOp sig;
 75     sig.addSigProcess(SIGCHLD, waitchlid);//回收進程
 76 
 77     while(1)
 78     {   
 79         if ((connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1)
 80         {  
 81             break;  
 82         } 
 83         else
 84         {
 85             if (0 == fork())
 86             {
 87                 close(socket_fd); //關閉監聽描述符
 88                 while(1)
 89                 {
 90                     memset(&buff,'\0', sizeof(buff));  
 91                     recv(connect_fd, buff, BUFFSIZE - 1, 0);  
 92                     send(connect_fd, buff, BUFFSIZE - 1, 0); 
 93                     printf("recv msg from client: %s\n", buff);
 94                 }
 95                 close(connect_fd);
 96                 exit(0);
 97             }          
 98             close(connect_fd);//父進程關閉連接描述符        
 99         }
100     }  
101     close(connect_fd);    
102     close(socket_fd);  
103 }  

之前提到過listen描述符和connect描述符是完全獨立的,關閉都不會影響另一個描述符工作。所以在代碼中,父子進程都會關閉不需要的描述符。

測試結果如下:

ps -aux查看系統進程,可以看到三個進程,一個是主進程用於listen監聽,兩個子進程進行通信。

下面是線程並發實現:

 1 /* File Name: server.c */  
 2 #include <stdio.h>  
 3 #include <stdlib.h>  
 4 #include <string.h>  
 5 #include <unistd.h>  
 6 #include <pthread.h>  
 7 #include <errno.h>  
 8 #include <sys/types.h>  
 9 #include <sys/socket.h>  
10 #include <sys/unistd.h>
11 #include <netinet/in.h>  
12 
13 const int DEFAULT_PORT = 2500;  
14 const int BUFFSIZE = 1024;
15 const int MAXLINK = 10;
16 
17 void* run(void* pConnect_fd)
18 {
19     char buff[BUFFSIZE]; 
20     int *connect_fd = reinterpret_cast<int *>(pConnect_fd);
21     pthread_detach(pthread_self());
22     while(1)
23     {
24         memset(&buff,'\0', sizeof(buff));  
25         recv(*connect_fd, buff, BUFFSIZE - 1, 0);  
26         send(*connect_fd, buff, BUFFSIZE - 1, 0); 
27         printf("recv msg from client: %s\n", buff);
28     }    
29 }
30 
31 int main(int argc, char** argv)  
32 {  
33     int    socket_fd, connect_fd;  
34     struct sockaddr_in servaddr;  
35     
36     if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
37     {  
38         printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
39         exit(0);  
40     } 
41     
42     memset(&servaddr, 0, sizeof(servaddr));  
43     servaddr.sin_family = AF_INET;  
44     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
45     servaddr.sin_port = htons(DEFAULT_PORT);
46   
47     if (bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
48     {  
49         printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
50         exit(0);  
51     }  
52     
53     if (listen(socket_fd, MAXLINK) == -1)
54     {  
55         exit(0);  
56     }   
57 
58     while(1)
59     {   
60         if ((connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1)
61         {  
62             break;  
63         } 
64         else    //accept獲取到描述符創建線程
65         {
66             pthread_t _Tread;
67             pthread_create(&_Tread, NULL, run, &connect_fd);//傳參為連接描述符
68         }
69     }  
70     close(connect_fd);    
71     close(socket_fd);  
72 }  

測試結果如下:

效果和進程一樣,執行netstat查看tcp狀態

兩組連接相互通信。

線程並發和進程並發各有優劣,目前大多服務器還是用線程進行並發的,進程要對父進程進行拷貝,資源消耗大,但相互直接資源互不影響,線程效率高但是要注意鎖的使用,一個線程可能會影響整個服務器的運行。

詳細優缺點詳細可參考:http://blog.chinaunix.net/uid-20556054-id-3067672.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM