tcp回射服务器程序处理僵死进程

来源:互联网 发布:暗黑破坏神2 mac 双开 编辑:程序博客网 时间:2024/06/06 11:39

tcp回射服务器程序处理僵死进程

什么是僵死进程

当子进程调用exit指令退出的时候,会留下一个一个成为僵死(zombie)的数据结构,目的是维护子进程的信息,以便父进程在以后的某个时候获取,这些信息包括子进程的进程ID,终止状态以及资源利用信息等。在退出时他会向父进程发送SIGCHLD信号,如果父进程没有对该信号进行处理,该退出的子进程就会一直处于僵死状态,占用内核中的空间,多了以后甚至会导致我们耗尽进程资源。

如果tcp回射服务器不对SIGCHLD信号进行处理

tcp回射服务器程序如下(摘录自《UNIX网络编程 卷一》)

#include    "unp.h"intmain(int argc, char **argv){    int                 listenfd, connfd;    pid_t               childpid;    socklen_t           clilen;    struct sockaddr_in  cliaddr, servaddr;    listenfd = Socket(AF_INET, SOCK_STREAM, 0);    bzero(&servaddr, sizeof(servaddr));    servaddr.sin_family      = AF_INET;    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    servaddr.sin_port        = htons(SERV_PORT);    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));    Listen(listenfd, LISTENQ);    for ( ; ; ) {        clilen = sizeof(cliaddr);        connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);        if ( (childpid = Fork()) == 0) {    /* child process */            Close(listenfd);    /* close listening socket */            str_echo(connfd);   /* process the request */            exit(0);        }        Close(connfd);          /* parent closes connected socket */    }}

以上代码段未对SIGCHLD信号进行处理,所以导致我们键入EOF字符来终止客户时候,对应该套接字的子进程就会一直处于僵死状态。
把两个客户端程序关闭,父进程没有对子进程发来的SIGCHLD信号处理导致子进程一直处于僵死状态

把两个客户端程序关闭,父进程没有对子进程发来的SIGCHLD信号处理导致子进程一直处于僵死状态。

对SIGCHLD信号进行处理

父进程接收到SIGCHLD信号后调用wait或waitpid函数来为子进程”收尸”。这两个函数的原型如下:

#include<sys/wait.h>#include    "unp.h"pid_t wait(int *statloc);pid_t waitpid(pid_t pid,int *statloc,int options);/*这两个函数均返回两个值:已终止的进程ID号和通过statloc指针返回的子进程终止状态。通过设置options,我们可以指定附加选项,常用的选项是WNOHANG,它告知内核在没有已终止子进程时不要阻塞。pid参数允许我们制定想等待的进程ID,如果值为-1则表示等待第一个终止的进程。*//*如果调用wait的进程没有已终止的子进程,不过有一个或多个子进程仍在执行,那么wait将阻塞到现有的子进程第一个终止为止*/void sig_chld(int signo) {    pid_t pid;    int stat;    pid = wait(&stat);    printf("chlild %d terminated\n",pid);    return;}//如果调用waitpid则可以通过循环等待所有子进程结束void sig_chld(int signo) {    pid_t pid;    int stat;    while(pid = waitpid(-1,&stat,WNOHANG)) > 0)        printf("child %d terminated\n",pid);    return;}

我们应该使用wait还是waitpid?答案肯定是waitpid。我们假设一个情景:客户端程序通过循环与服务器建立了5个TCP连接,然后退出客户端程序,这5个连接几乎在同一时刻发给父进程SIGCHLD信号。因为Unix信号一般是不排队的,信号处理函数只执行一次。所以,如果使用wait,处理完第一个信号,信号处理函数就返回了,导致其他四个信号没有得到处理,还是导致了僵死。如果用waitpid的时候,我们可以通过一个循环,处理所有SIGCHLD信号,这样就没有僵死信号存留了。
注意:我们应该在listen调用之后增加该信号处理函数
Signal(SIGCHLD,sig_chld);
这必须在fork第一个子进程之前完成,且只做一次)

编写捕获信号的网络程序时,必须认清被中断的系统调用并处理他们

我们的服务器程序阻塞于慢系统调用(accept)捕获该信号,内核就会使accept返回一个EINTR错误(被中断的系统调用)而如果父进程不处理该错误,就会被中止。对于那些可能永远阻塞的函数,我们可以称之为慢系统调用。有些内核会自动重启被中断的系统调用,有些不会。所以为了程序的健壮性。我们要做的事情就是自己重启被中断的系统调用。

//在判断到EINTR错误的时候,执行continue返回循环重启accept//因为本文的程序基本摘抄自《Unix网络编程 卷一》所以读者如过要//编译这些代码的话需要该书提供的一些库for( ; ;) {    clilen = sizeof(cliaddr);    if( (connfd = accept(listenfd, (SA*) &cliaddr, &clilen) < 0) {    if(errno == EINTR)        continue;    }    else         err_sys("accept error");}

总结

我们在网络编程的时候要注意这三种情况:

  • 当fork子进程时,必须捕获SIGCHLD信号
  • 当捕获信号时,必须处理被中断的系统调用
  • SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免下僵死进程。

    最后贴出注意以上三点后的服务器程序代码

#include    "unp.h"intmain(int argc, char **argv){    int                 listenfd, connfd;    pid_t               childpid;    socklen_t           clilen;    struct sockaddr_in  cliaddr, servaddr;    void                sig_chld(int);    listenfd = Socket(AF_INET, SOCK_STREAM, 0);    bzero(&servaddr, sizeof(servaddr));    servaddr.sin_family      = AF_INET;    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    servaddr.sin_port        = htons(SERV_PORT);    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));    Listen(listenfd, LISTENQ);    Signal(SIGCHLD, sig_chld);  /* must call waitpid() */    for ( ; ; ) {        clilen = sizeof(cliaddr);        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {            if (errno == EINTR)                continue;       /* back to for() */            else                err_sys("accept error");        }        if ( (childpid = Fork()) == 0) {    /* child process */            Close(listenfd);    /* close listening socket */            str_echo(connfd);   /* process the request */            exit(0);        }        Close(connfd);          /* parent closes connected socket */    }}
1 0
原创粉丝点击