服务器线程并发和进程并发

进程和线程的使用在前面博文已经讲述完毕,在完成一个最简单的服务器之后,就是要考虑下如何实现并发服务器了。

要实现服务的并发,只能通过进程和线程两种方式。

之前提到过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, '', 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)
",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)
",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,'', 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
", 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,'', 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
", 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)
",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)
",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

原文地址:https://www.cnblogs.com/binchen-china/p/5487307.html