• 僵尸进程与SIGCHLD信号



    什么是僵尸进程?

    首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。

    而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。

    任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。

     

    僵尸进程的目的?

    设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。


    如何避免僵尸进程? 1、通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。 2、父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。 3、如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。 4、通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。 第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

       如果服务器端因为客户端的关闭,循环开启的处理子进程会成为僵尸进程。当有5个子进程退出,就会有5个SIGCHLD  信号发给父进程。当第一个信号过来,父进程处于sighandler过程,在此过程中,若有其他信号过来,信号(不可靠信号)会丢失,只排队一个。所以最终只会处理两个SIGCHLD信号,剩3个僵尸进程;若是5个一起来,可能只处理第一个(剩4个僵尸进程)。用while解决

    /*
    服务端每次会fork一个子进程处理客户单请求,每个客户端断开连接,会有一个子进程成为僵尸进程(父进程不处理的话)
    */
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<string.h>
    #include<stdlib.h>
    #include<stdio.h>
    #include<errno.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<signal.h>
    #include<sys/wait.h>
    #define ERR_EXIT(m)
        do
        {
            perror(m);
            exit(EXIT_FAILURE);
        }while(0)
    ssize_t readn(int fd,void *buf,size_t count)
    {
        size_t nleft=count;
        ssize_t nread;
        char *bufp=(char*)buf;
        while(nleft>0)
        {
            if((nread=read(fd,bufp,nleft))<0)
            {
                if(errno==EINTR)
                    continue;
                else
                    return -1;
            }
            else if(nread==0)
                return (count-nleft);
            bufp+=nread;
            nleft-=nread;
        }
        return count;
    }
    ssize_t writen(int fd, const void *buf, size_t count)
    {
        size_t nleft=count;
        ssize_t nwritten;
        char *bufp=(char*)buf;
        while(nleft>0)
        {
            if((nwritten=write(fd,bufp,nleft))<=0)
            {
                if(errno==EINTR)
                    continue;
                return -1;
            }else if(nwritten==0)
                continue;
            bufp+=nwritten;
            nleft-=nwritten;
        }
        return count;
    
    }
    ssize_t recv_peek(int sockfd,void *buf,size_t len)
    {
        while(1)
        {
            int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
            if(ret==-1&&errno==EINTR)
                continue;
            return ret;
        }
    }
    //偷窥方案实现readline避免一次读取一个字符
    ssize_t readline(int sockfd,void * buf,size_t maxline)
    {
        int ret;
        int nread;
        size_t nleft=maxline;
        char *bufp=(char*)buf;
        while(1)
        {
            ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
            if(ret<0)
                return ret;
            else if(ret==0)
                return ret;
            nread=ret;
            int i;
            for(i=0;i<nread;i++)
            {
                if(bufp[i]=='
    ')
                {
                    ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
                    if(ret!=i+1)
                        exit(EXIT_FAILURE);
                    return ret;
                }
            }
            if(nread>nleft)
                exit(EXIT_FAILURE);
            nleft-=nread;
            ret=readn(sockfd,bufp,nread);
            if(ret!=nread)
                exit(EXIT_FAILURE);
            bufp+=nread;//移动指针继续窥看
        }
        return -1;
    }
    void echo_srv(int conn)
    {
            int ret;
            char recvbuf[1024];
            while(1)
            {
                memset(&recvbuf,0,sizeof(recvbuf));
                //使用readn之后客户端发送的数据不足1024会阻塞
                //在客户端程序中确定消息的边界,发送定长包
                ret=readline(conn,recvbuf,1024);                                                               
                //客户端关闭
                if(ret==-1)
                    ERR_EXIT("readline");            
                else if(ret==0)
                {
                    printf("client close
    ");
                    break;//不用继续循环等待客户端数据
                }
                fputs(recvbuf,stdout);
                writen(conn,recvbuf,strlen(recvbuf));
            }
    }
    void handle_sigchld(int sig)
    {
        //法一 :只能等待第一个,不能等待所有的子进程,通常剩余3个僵尸进程。剩余3个说明不可靠信号排队一个
        //wait(NULL);
        //当没有子进程退出,返回-1
        while(waitpid(-1,NULL, WNOHANG)>0)
            ;//pid==-1等待所有子进程结束。等到一个子进程返回值大于0
    }
    int main(void)
    {    
        //方法一:
        //signal(SIGCHLD,SIG_IGN);//父进程忽略子进程终止信号,解决僵尸进程
        /*方法二 */
        signal(SIGCHLD,handle_sigchld);
        int listenfd;
        if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
            ERR_EXIT("socket error");
        //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0)
        
    
        //本地协议地址赋给一个套接字
        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);//表示本机地址
    
        //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
        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)//设置监听套接字(被动套接字)
            ERR_EXIT("listen error");
        
        struct sockaddr_in peeraddr;//对方套接字地址
        socklen_t peerlen=sizeof(peeraddr);
        int conn;//已连接套接字(主动套接字)
        pid_t pid;
        while(1){
            if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
                ERR_EXIT("accept error");
            //连接好之后就构成连接,端口是客户端的。peeraddr是对端
            printf("ip=%s port=%d
    ",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
            pid=fork();
            if(pid==-1)
                ERR_EXIT("fork");
            if(pid==0){    
                    close(listenfd);
                    echo_srv(conn);
                    //某个客户端关闭,结束该子进程,否则子进程也去接受连接
                    //虽然结束了exit退出,但是内核还保留了其信息,父进程并未为其收尸。
                    exit(EXIT_SUCCESS);
            }else     close(conn);
        }
        return 0;
    }
     

    //客户端:建立5个连接请求服务端,再关闭。

    //五个客户端去连接并发服务器 
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<string.h>
    #include<stdlib.h>
    #include<stdio.h>
    #include<errno.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<signal.h>
    #define ERR_EXIT(m)
        do
        {
            perror(m);
            exit(EXIT_FAILURE);
        }while(0)
    ssize_t readn(int fd,void *buf,size_t count)
    {
        size_t nleft=count;
        ssize_t nread;
        char *bufp=(char*)buf;
        while(nleft>0)
        {
            if((nread=read(fd,bufp,nleft))<0)
            {
                if(errno==EINTR)
                    continue;
                else
                    return -1;
            }
            else if(nread==0)
                return (count-nleft);
            bufp+=nread;
            nleft-=nread;
        }
        return count;
    }
    ssize_t writen(int fd, const void *buf, size_t count)
    {
        size_t nleft=count;
        ssize_t nwritten;
        char *bufp=(char*)buf;
        while(nleft>0)
        {
            if((nwritten=write(fd,bufp,nleft))<=0)
            {
                if(errno==EINTR)
                    continue;
                return -1;
            }else if(nwritten==0)
                continue;
            bufp+=nwritten;
            nleft-=nwritten;
        }
        return count;
    
    }
    ssize_t recv_peek(int sockfd,void *buf,size_t len)
    {
        while(1)
        {
            int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
            if(ret==-1&&errno==EINTR)
                continue;
            return ret;
        }
    }
    //偷窥方案实现readline避免一次读取一个字符
    ssize_t readline(int sockfd,void * buf,size_t maxline)
    {
        int ret;
        int nread;
        size_t nleft=maxline;
        char *bufp=(char*)buf;
        while(1)
        {
            ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
            if(ret<0)
                return ret;
            else if(ret==0)
                return ret;
            nread=ret;
            int i;
            for(i=0;i<nread;i++)
            {
                if(bufp[i]=='
    ')
                {
                    ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
                    if(ret!=i+1)
                        exit(EXIT_FAILURE);
                    return ret;
                }
            }
            if(nread>nleft)
                exit(EXIT_FAILURE);
            nleft-=nread;
            ret=readn(sockfd,bufp,nread);
            if(ret!=nread)
                exit(EXIT_FAILURE);
            bufp+=nread;//移动指针继续窥看
        }
        return -1;
    }
    void echo_cli(int sock)
    {
        char sendbuf[1024]={0};
        char recvbuf[1024]={0};    
        while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)//默认有换行符
        {
            writen(sock,sendbuf,strlen(sendbuf));
            int ret=readline(sock,recvbuf,1024);
            if(ret==-1)
                ERR_EXIT("readline");
            else if(ret==0)
            {
                printf("service closed
    ");
                break;
            }
            fputs(recvbuf,stdout);
            memset(sendbuf,0,sizeof(sendbuf));
            memset(recvbuf,0,sizeof(recvbuf));
        }
        close(sock);
    }
    int main(void)
    {
        int sock[5];//客户端创建5个套接字
        int i=0;
        for(i=0;i<5;i++)
        {
            if((sock[i]=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<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=inet_addr("127.0.0.1");//服务器段地址
            //inet_aton("127.0.0.1",&servaddr.sin_addr);
        
            if(connect(sock[i],(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
                ERR_EXIT("connect");
    
            //利用getsockname获取客户端本身地址和端口,即为对方accept中的对方套接口
            struct sockaddr_in localaddr;
            socklen_t addrlen=sizeof(localaddr);
            if(getsockname(sock[i],(struct sockaddr *)&localaddr,&addrlen)<0)
                ERR_EXIT("getsockname error");
            printf("local IP=%s, local port=%d
    ",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));    
            //使用getpeername获取对方地址
        
            
        }
        echo_cli(sock[0]);//选择一个与服务器通信
        return 0;
    }
  • 相关阅读:
    SSM,即Spring+SpringMVC+MyBatis三个开源框架的整合框架集。
    doAfterBody()方法是在( )接口中定义的。
    考虑下面两个JSP文件代码片断: test1.jsp:
    下列JSP代码:
    以下web.xml片断( )正确地声明servlet 上下文参数。
    以下( )可用于检索session属性userid的值。
    有关Servlet的生命周期说法正确的有( )。
    关于Web应用程序,下列说法错误的是( )。
    如何创建Cookie? (选择1项)
    HttpServlet中,用来处理POST请求的方法是(选择1项)
  • 原文地址:https://www.cnblogs.com/wsw-seu/p/13463802.html
Copyright © 2020-2023  润新知