zoukankan      html  css  js  c++  java
  • linux网络编程之socket:使用fork并发处理多个client的请求

    在回射客户/服务器程序中,服务器只能处理一个客户端的请求,如何同时服务多个客户端呢?在未讲到select/poll/epoll等高级IO之前,比较老土的办法是使用fork来实现。网络服务器通常用fork来同时服务多个客户端,父进程专门负责监听端口,每次accept一个新的客户端连接就fork出一个子进程专门服务这个客户端。但是子进程退出时会产生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程,最简单的办法就是直接忽略SIGCHLD信号。

    双击代码全选
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    /*************************************************************************
        > File Name: echoser.c
        > Author: Simba
        > Mail: dameng34@163.com
        > Created Time: Fri 01 Mar 2013 06:15:27 PM CST
     ************************************************************************/
         
    #include<stdio.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<signal.h>
         
    #define ERR_EXIT(m)
        do {
            perror(m);
            exit(EXIT_FAILURE);
        } while (0)
         
    void do_service(int);
         
    int main(void)
    {
        signal(SIGCHLD, SIG_IGN);
        int listenfd; //被动套接字(文件描述符),即只可以accept, 监听套接字
        if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
            //  listenfd = socket(AF_INET, SOCK_STREAM, 0)
            ERR_EXIT("socket error");
         
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
        /* inet_aton("127.0.0.1", &servaddr.sin_addr); */
         
        int on = 1;
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
            ERR_EXIT("setsockopt error");
         
        if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("bind error");
         
        if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
            ERR_EXIT("listen error");
         
        struct sockaddr_in peeraddr; //传出参数
        socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值
        int conn; // 已连接套接字(变为主动套接字,即可以主动connect)
         
        pid_t pid;
         
        while (1)
        {
            if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0) //3次握手完成的序列
                ERR_EXIT("accept error");
            printf("recv connect ip=%s port=%dn", inet_ntoa(peeraddr.sin_addr),
                   ntohs(peeraddr.sin_port));
         
            pid = fork();
            if (pid == -1)
                ERR_EXIT("fork error");
            if (pid == 0)
            {
                // 子进程
                close(listenfd);
                do_service(conn);
                exit(EXIT_SUCCESS);
            }
            else
                close(conn); //父进程
        }
         
        return 0;
    }
         
    void do_service(int conn)
    {
        char recvbuf[1024];
        while (1)
        {
            memset(recvbuf, 0, sizeof(recvbuf));
            int ret = read(conn, recvbuf, sizeof(recvbuf));
            if (ret == 0)   //客户端关闭了
            {
                printf("client closen");
                break;
            }
            else if (ret == -1)
                ERR_EXIT("read error");
            fputs(recvbuf, stdout);
            write(conn, recvbuf, ret);
        }
    }

    上述程序利用了一点,就是父子进程共享打开的文件描述符,因为在子进程已经用不到监听描述符,故将其关闭,而连接描述符对父进程也没价值,将其关闭。当某个客户端关闭,则read 返回0,退出循环,子进程顺便exit,但如果没有设置对SIGCHLD信号的忽略,则因为父进程还没退出,故子进程会变成僵尸进程。

    现在先运行server,再打开另外两个终端,运行client(直接用回射客户/服务器程序中的客户端程序),可以看到server输出如下:

    simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_fork 
    recv connect ip=127.0.0.1 port=46452
    recv connect ip=127.0.0.1 port=46453

    在另一个终端ps一下:

    simba@ubuntu:~$ ps aux | grep echoser
    simba     3300  0.0  0.0   2008   280 pts/0    S+   22:10   0:00 ./echoser_fork
    simba     3303  0.0  0.0   2008    60 pts/0    S+   22:10   0:00 ./echoser_fork
    simba     3305  0.0  0.0   2008    60 pts/0    S+   22:10   0:00 ./echoser_fork
    simba     3313  0.0  0.0   4392   836 pts/3    S+   22:12   0:00 grep --color=auto echoser
    simba@ubuntu:~$ 

    发现共有3个进程,其中一个是父进程处于监听中,另外两个是子进程处于对客户端服务中,现在ctrl+c 掉其中一个client,由上面的分析可知对应服务的子进程也会退出,而因为我们设置了父进程对SIGCHLD信号进行忽略,故不会产生僵尸进程,输出如下:

    simba@ubuntu:~$ ps aux | grep echoser
    simba     3300  0.0  0.0   2008   280 pts/0    S+   22:10   0:00 ./echoser_fork
    simba     3305  0.0  0.0   2008    60 pts/0    S+   22:10   0:00 ./echoser_fork
    simba     3321  0.0  0.0   4392   836 pts/3    S+   22:13   0:00 grep --color=auto echoser

    如果把29行代码注释掉,上述的情景输出可能为:

    simba@ubuntu:~$ ps aux | grep echoser
    simba     3125  0.0  0.0   2004   280 pts/0    S+   21:38   0:00 ./echoser_fork
    simba     3128  0.0  0.0      0     0 pts/0    Z+   21:38   0:00 [echoser_fork] <defunct>
    simba     3130  0.0  0.0   2004    60 pts/0    S+   21:38   0:00 ./echoser_fork
    simba     3141  0.0  0.0   4392   832 pts/3    S+   21:40   0:00 grep --color=auto echoser

    即子进程退出后变成了僵尸进程。

    如果不想忽略SIGCHLD信号,则必须在信号处理函数中调用wait处理,但这里需要注意的是wait只能等待第一个退出的子进程,所以这里需要使用waitpid函数,如下所示:

    双击代码全选
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    signal(SIGCHLD, handler);
    .....................
         
    void handler(int sig)
    {
        /*  wait(NULL); //只能等待第一个退出的子进程 */
        /* 即使因为几个连接同时断开,信号因不能排队而父进程只收到一个信号
         * 直到已经waitpid到所有子进程,返回0,才退出循环 */
        while (waitpid(-1, NULL, WNOHANG) > 0)
            ;
    }

    实际上使用 while (wait(NULL) > 0) ; 也可以达到同样的效果。

    来源: csdn   作者:Simba888888

  • 相关阅读:
    Eclipse 导入项目乱码问题(中文乱码)
    sql中视图视图的作用
    Java基础-super关键字与this关键字
    Android LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot)的参数理解
    Android View和ViewGroup
    工厂方法模式(java 设计模式)
    设计模式(java) 单例模式 单例类
    eclipse乱码解决方法
    No resource found that matches the given name 'Theme.AppCompat.Light 的完美解决方案
    【转】使用 Eclipse 调试 Java 程序的 10 个技巧
  • 原文地址:https://www.cnblogs.com/huazhen/p/3419481.html
Copyright © 2011-2022 走看看