• Linux客户/服务器程序设计范式——阿帕奇服务器(多进程)


    引言

    本文会写一个并发服务器(concurrent server)程序,它为每个客户请求fork出一个子进程。

    注意

    1. 信号处理问题

    对于相同信号,按信号的先后顺序依次处理。可能会产生的问题是,正在处理sig1信号时,又来了2个或更多的sig1信号,此sig1时只会在处理完原来的sig1信号后,再处理1个sig1信号。因此对于相同信号,会产生信号掉包的问题。 一个儿子退了之后,程序在处理handler(),如果此时又退了两个儿子,那么必然有一个儿子的资源回收不到,称为僵尸进程。

    对于不同信号,优先处理后者,处理完后者在回头处理上一个。例如正在处理sig1时,来了sig2,则会先处理sig2,等处理完sig2后,回过头继续处理sig1。

    2. 解决方案

    在注册的信号处理函数中,使用循环,并在循环中用waitpid来回收子进程资源。只要进入信号处理函数,那么该信号处理函数就可以把所有fork出的儿子的资源都回收掉。注意:waitpid要设置成非阻塞模式,不然当进入循环后,如果没有子进程退出时,会阻塞在信号处理函数中。

    3. wait与waitpid

    wait一定是阻塞模式因此在信号处理函数中,用while1{wait(NULL)}有问题,如果进入信号处理函数后,只要有儿子不退,就会一直阻塞在这里。

    waitpid可以是阻塞模式,也可以是非阻塞模式。

    4. select与accept

    两者在收到信号时,均会返回-1。

    对于系统默认不处理的信号,程序收不到,两者当然也不会返回-1。换句话说,SIGCHLD是系统默认不处理的信号,如果不对其注册信号处理函数,当子进程退出时,select与accept也是不会返回-1的。

    代码

    server.c

     7 #include "my_socket.h"
     8 #include <sys/wait.h>
     9 #include <signal.h>
    10 #include <errno.h>
    11 #define MY_IP "192.168.1.100"
    12 #define MY_PORT 8888
    13 #define SIZE 192
    14 #define MSG_SIZE (SIZE - 4)
    15 extern int errno ;
    16 typedef struct tag_mag
    17 {
    18     int  msg_len;//记录msg_buf的真实大小
    19     char msg_buf[MSG_SIZE];//msg_buf所占空间为188byte
    20 }MSG, *pMSG;
    21 
    22 void my_handle(int num)
    23 {
    24     /*waitpid参数:
    25      * -1表示回收每一个儿子
    26      * NULL表示不关心子进程的exit的返回值
    27      * WNOHANG:wait no hang 非阻塞模式
    28      *waitpid返回值:
    29      * -1表示没有创建任何儿子
    30      *  0表示没有儿子退出
    31      *大于0表示有儿子退出
    32      * */
    33     while(waitpid(-1, NULL, WNOHANG ) > 0) ;
    34 }
    35 
    36 int main(int argc, char* argv[])
    37 {
    38     int fd_listen , fd_client ;
    39     signal(SIGCHLD, my_handle);
    40     my_socket(&fd_listen, MY_TCP, MY_IP ,MY_PORT);
    41     my_listen(fd_listen, 10);
    42 
    43     while( fd_client = accept(fd_listen, NULL, NULL))
    44     {
    45         /* 只要不是程序默认忽略的信号,accept都能收到,并返回-1 */
    46         if(fd_client == -1)
    47         {
    48             if(errno == EINTR)
    49             {
    50                 continue ;
    51             }else 
    52             {
    53                 break ; //break退出后,父亲就退了。下来儿子会由init接管。
    54             }
    55         }else 
    56         {
    57             if(fork() == 0) //fork儿子用于与客户端通信
    58             {
    59                 MSG recv_msg ;
    60                 int recvn;
    61                     while(1 )
    62                     {
    63                         memset(&recv_msg, 0, sizeof(MSG));
    64                         /*在my_socket.c中,my_recv接收的长度 与 my_send 发送的长度必须是精确值 
    65                          * my_recv中填的长度小于等于实际要收的,是可以的,大于的话就永远退不出循环了*/
    66                         my_recv(&recvn, fd_client, &recv_msg, 4);
    67                         if(recvn == 0) //当对面客户端退出(关闭socket),系统调用recv的返回值为0
    68                         {
    69                             break ;
    70                         }else
    71                         {
    72                             my_recv(NULL,fd_client, &recv_msg.msg_buf, recv_msg.msg_len);
    73                             my_send(NULL, fd_client, &recv_msg, 4 + recv_msg.msg_len);
    74 
    75                         }
    76                     }
    77                 close(fd_client);
    78                 exit(0);    
    79             }
    80             close(fd_client);
    81         }
    82     }
    83     return 0 ;
    84 }

    client.c

     1 #include "my_socket.h"
     2 #define MY_IP "192.168.1.100"
     3 #define MY_PORT 6666
     4 #define SER_IP "192.168.1.100"
     5 #define SER_PORT 8888
     6 #define SIZE 192
     7 #define MSG_SIZE (SIZE - 4)
     8 typedef struct tag_mag 
     9 {
    10     int msg_len ;
    11     char msg_buf[MSG_SIZE];//188
    12 }MSG, *pMSG;
    13 int main(int argc, char* argv[])
    14 {
    15     int sfd ;
    16     my_socket(&sfd, MY_TCP, MY_IP, MY_PORT);
    17     my_connect(sfd, SER_IP, SER_PORT);
    18     MSG my_msg ;
    19     while(memset(&my_msg, 0, sizeof(MSG)), fgets(my_msg.msg_buf, MSG_SIZE, stdin)!= NULL)
    20     {
    21         my_msg.msg_len = strlen(my_msg.msg_buf);
    22         my_send(NULL, sfd, &my_msg, 4 + my_msg.msg_len );
    23         memset(&my_msg, 0, sizeof(MSG));
    24         my_recv(NULL, sfd, &my_msg, 4);
    25         my_recv(NULL, sfd, &my_msg.msg_buf, my_msg.msg_len);
    26         printf("recv from server : %s 
    ", my_msg.msg_buf);
    27     
    28     }
    29     close(sfd);
    30 
    31 }

    注意:本代码由于client.c中绑定了端口,因此只能连一个客户端,读者可以自己从命令行中输入端口号或者让系统自行分配。

    本范式的缺陷在于,服务端在每次收到一个客户端连接请求后,才会fork儿子进行处理,fork有时间开销。更好的方法是,服务端提前fork好儿子,即进程池。

  • 相关阅读:
    Meten Special Activities II
    Meten Special Activities II
    Meten Special Activities II
    Meten Special Activities II
    Meten Special Activities
    Meten Special Activities
    Meten Special Activities
    Meten Special Activities
    Meten Special Activities
    冒泡排序和选择排序
  • 原文地址:https://www.cnblogs.com/DLzhang/p/4020914.html
Copyright © 2020-2023  润新知