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


    在上一篇程序框架中,解决了子进程退出,父进程继续存在的功能,但是多条客户端连接如果同一时间并行退出,
    导致服务器端多个子进程同一时间全部退出,而SIGCHLD是不可靠信号,同时来多条信号可能无法处理,导致出现僵尸进程,
    如果使用while循环wait又会阻塞父进程,这里采取waitpid()函数来解决这个问题。
    //辅助类实现
    #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 !
    ");
            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 !
    ");
                //返回已经读取的字节数
                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 !
    ");
            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 !
    ");
                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 !
    ");
            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 !
    ");
                return -1;
            }
            for (i = 0; i < nread; i++)
            {
                if (pbuf[i] == '
    ')
                {
                    //这是一段报文
                    memset(pbuf, 0, count);
                    //从socket缓存区读取i+1个字节
                    ret = readn(fd, pbuf, i + 1);
                    if (ret != i + 1)
                        return -1;
                    return ret;
                }
            }
            //如果当前socket缓存区中没有
    ,
            //那么先判断自定义buf是否还有空间,如果没有空间,直接退出
            //如果有空间,先将当前socket缓存区中的数据读出来,放入buf中,清空socket缓存
            //继续recv,判断下一段报文有没有
    
            if (lread >= count)
            {
                printf("自定义buf太小了!
    ");
                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
    ",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 !
    ");
            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
    ", 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
    ",inet_ntoa(myaddr.sin_addr));
        }
        client_say(sockarr[0]);
        return 0;
    }
  • 相关阅读:
    迷宫广搜
    通过地址获取百度地图经纬度
    图片上传存储数据库
    spring boot加载配置文件的顺序
    阿里 短信认证
    spring boot properties文件与yaml文件的区别
    springboot pom问题及注解
    手机短信认证
    获取class对象的三种方法以及通过Class对象获取某个类中变量,方法,访问成员
    关于mysql优化问题
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/6141381.html
Copyright © 2020-2023  润新知