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


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

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

    之前提到过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

  • 相关阅读:
    eclipse安装m2e
    Ubuntu安装Maven(转)
    父亲节点的实现
    vscode go linux 依赖包
    golang DHCPv4/v6 demo
    解决golang.org模块无法下载的问题
    go 网络数据包分析(3)
    go 网络数据包分析(2)
    Go语言:判断IP是否合法是IPv4还是IPv6
    go IP地址转化为二进制数
  • 原文地址:https://www.cnblogs.com/binchen-china/p/5487307.html
Copyright © 2020-2023  润新知