Linux 网络编程详解七(并发僵尸进程处理)

在上一篇程序框架中,解决了子进程退出,父进程继续存在的功能,但是多条客户端连接如果同一时间并行退出,
导致服务器端多个子进程同一时间全部退出,而SIGCHLD是不可靠信号,同时来多条信号可能无法处理,导致出现僵尸进程,
如果使用while循环wait又会阻塞父进程,这里采取waitpid()函数来解决这个问题。

强调:后来经过实践,发现该方案在大并发时也有问题,例子中只有2个子进程,所以貌似可以解决问题,经过测试如果同时有上百个子进程同时退出,
仍然会出现SIGCHLD信号丢失情况

因此批量kill子进程的时候必须确认kill成功之后再kill下一个子进程(可以使用kill -0 pid来确认)。
//辅助类实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "pub.h"

ssize_t readn(int fd, const void *buf, ssize_t count)
{
    if (buf == NULL)
    {
        printf("readn() params is not correct !\n");
        return -1;
    }
    //定义剩余字节数
    ssize_t lread = count;
    //定义辅助指针变量
    char *pbuf = (char *) buf;
    //定义每次读取的字节数
    ssize_t nread = 0;
    while (lread > 0)
    {
        nread = read(fd, pbuf, lread);
        if (nread == -1)
        {
            //read是可中断睡眠函数,需要屏蔽信号
            if (errno == EINTR)
                continue;
            perror("read() err");
            return -1;
        } else if (nread == 0)
        {
            printf("peer read socket is closed !\n");
            //返回已经读取的字节数
            return count - lread;
        }
        //重置剩余字节数
        lread -= nread;
        //辅助指针后移
        pbuf += nread;
    }
    return count;
}

ssize_t writen(int fd, const void *buf, ssize_t count)
{
    if (buf == NULL)
    {
        printf("writen() params is not correct !\n");
        return -1;
    }
    //定于剩余字节数
    ssize_t lwrite = count;
    //定义每次写入字节数
    ssize_t nwrite = 0;
    //定义辅助指针变量
    char *pbuf = (char *) buf;
    while (lwrite > 0)
    {
        nwrite = write(fd, pbuf, lwrite);
        if (nwrite == -1)
        {
            if (errno == EINTR)
                continue;
            perror("write() err");
            return -1;
        } else if (nwrite == 0)
        {
            printf("peer write socket is closed !\n");
            return count - lwrite;
        }
        //重置剩余字节数
        lwrite -= nwrite;
        //辅助指针变量后移
        pbuf += nwrite;
    }
    return count;
}

ssize_t recv_peek(int fd, const void *buf, ssize_t count)
{
    if (buf == NULL)
    {
        printf("recv_peek() params is not correct !\n");
        return -1;
    }
    ssize_t ret = 0;
    while (1)
    {
        //此处有多少读取多少,不一定ret==count
        ret = recv(fd, (void *) buf, count, MSG_PEEK);
        if (ret == -1 && errno == EINTR)
            continue;
        return ret;
    }
    return -1;
}

ssize_t mreadline(int fd, const void *buf, ssize_t count)
{
    //定义剩余字节数
    ssize_t lread = count;
    //定义每次读取的字节数
    ssize_t nread = 0;
    //定义辅助指针变量
    char *pbuf = (char *) buf;
    int i = 0, ret = 0;
    while (1)
    {
        nread = recv_peek(fd, pbuf, count);
        if (nread == -1)
        {
            perror("recv_peek() err");
            return -1;
        } else if (nread == 0)
        {
            //注意:这里一个客户端有两个进程,也就是套接字会关闭两次,会向服务器发送两次FIN信号
            printf("peer socket is closed !\n");
            return -1;
        }
        for (i = 0; i < nread; i++)
        {
            if (pbuf[i] == '\n')
            {
                //这是一段报文
                memset(pbuf, 0, count);
                //从socket缓存区读取i+1个字节
                ret = readn(fd, pbuf, i + 1);
                if (ret != i + 1)
                    return -1;
                return ret;
            }
        }
        //如果当前socket缓存区中没有\n,
        //那么先判断自定义buf是否还有空间,如果没有空间,直接退出
        //如果有空间,先将当前socket缓存区中的数据读出来,放入buf中,清空socket缓存
        //继续recv,判断下一段报文有没有\n
        if (lread >= count)
        {
            printf("自定义buf太小了!\n");
            return -1;
        }
        //读取当前socket缓存
        ret = readn(fd, pbuf, nread);
        if (ret != nread)
            return -1;
        lread -= nread;
        pbuf += nread;
    }
    return -1;
}

void handler(int sign)
{
    if (sign == SIGCHLD)
    {
        int mypid=0;
        //WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
        while((mypid=waitpid(-1,NULL,WNOHANG))>0)
        {
            printf("子进程pid=%d\n",mypid);
        }
        //wait(NULL);
    }
}

int server_socket()
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    //reuseaddr
    int optval = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
            == -1)
    {
        perror("setsockopt() err");
        return -1;
    }
    //bind
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("bind() err");
        return -1;
    }
    //listen
    if (listen(listenfd, SOMAXCONN) == -1)
    {
        perror("listen()err");
        return -1;
    }
    pid_t pid = 0;
    //忽略SIGCHLD信号
    //signal(SIGCHLD,SIG_IGN);
    //安装信号
    if (signal(SIGCHLD, handler) == SIG_ERR)
    {
        printf("signal() failed !\n");
        return -1;
    }
    while (1)
    {
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);
        int conn = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen);
        printf("accept by %s\n", inet_ntoa(peeraddr.sin_addr));
        if (conn == -1)
        {
            perror("accept() err");
            return -1;
        }
        pid = fork();
        if (pid == -1)
        {
            perror("fork() err");
            return -1;
        }
        //子进程接收数据
        if (pid == 0)
        {
            //关闭监听套接字
            close(listenfd);
            char buf[1024] = { 0 };
            int ret = 0;
            while (1)
            {
                ret = mreadline(conn, buf, 1024);
                if (ret == -1)
                {
                    close(conn);
                    return -1;
                }
                //打印客户端数据
                fputs(buf, stdout);
                //把数据返回给客户端
                writen(conn, buf, ret);
                memset(buf, 0, sizeof(buf));
            }
        } else if (pid > 0)
        {
            close(conn);
        }
    }
    return 0;
}

int client_say(int fd)
{
    int ret = 0;
    char buf[1024] = { 0 };
    while (fgets(buf, 1024, stdin) != NULL)
    {
        //发送数据
        ret = writen(fd, buf, strlen(buf));
        if (ret != strlen(buf))
            return -1;
        memset(buf, 0, sizeof(buf));
        ret = mreadline(fd, buf, sizeof(buf));
        if (ret == -1)
        {
            return -1;
        }
        fputs(buf,stdout);
        memset(buf, 0, sizeof(buf));
    }
    return 0;
}

int client_socket()
{
    int sockarr[10] = { 0 };
    int i = 0;
    //同时创建5个连接
    for (i = 0; i < 5; i++)
    {
        sockarr[i] = socket(AF_INET, SOCK_STREAM, 0);
        if (sockarr[i] == -1)
        {
            perror("socket() err");
            return -1;
        }
        //bind
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8080);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        if (connect(sockarr[i], (struct sockaddr *) &addr, sizeof(addr)) == -1)
        {
            perror("connect() err");
            return -1;
        }
        //获取本机地址
        struct sockaddr_in myaddr;
        socklen_t mylen = sizeof(myaddr);
        if (getsockname(sockarr[i], (struct sockaddr *) &myaddr, &mylen) == -1)
        {
            perror("getsockname() err");
            return -1;
        }
        printf("本次连接地址:%s\n",inet_ntoa(myaddr.sin_addr));
    }
    client_say(sockarr[0]);
    return 0;
}

 

posted on 2016-12-07 15:34  寒魔影  阅读(664)  评论(0编辑  收藏  举报

导航