您的位置:首页 > 理论基础 > 计算机网络

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

2016-05-10 22:14 766 查看

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信号后调用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 */
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: