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

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

什么是僵死进程

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

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

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

#include    "unp.h"

int
main(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"

int
main(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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值