僵尸进程的产生
由下面一段cs架构的代码说明下,僵尸进程的产生,下面是一个简单的回射服务器,客户端负责从标准输入读入数据,写到服务端,服务端主进程监听连接套接字,fork一个子进程处理连接套接字,读入数据和回写给客户端。大写的函数只对出错的情况进行处理。
客户端代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "socketio.h"
int main()
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(5566);
Connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
char sendline[1024], recvline[1024];
memset(sendline, 0, sizeof(sendline));
memset(recvline, 0, sizeof(recvline));
while (fgets(sendline, sizeof(sendline), stdin) != NULL)
{
int nread = 0;
int nwrite = 0;
nwrite = write(sockfd, sendline, strlen(sendline));
if(nwrite < 0)
{
if (errno == EINTR)
continue;
perror("write");
}
nread = read(sockfd, recvline, sizeof(recvline));
if(nread < 0)
{
if (errno == EINTR)
continue;
perror("read");
}
if (nread == 0)
{
printf("server close\n");
// break;
}
fputs(recvline, stdout);
memset(sendline, 0, sizeof(sendline));
memset(recvline, 0, sizeof(recvline));
}
close(sockfd);
return 0;
}
服务端代码
#include
#include
#include
#include
#include
#include
#include "socketio.h"
int main()
{
struct sockaddr_in servaddr, cliaddr;
socklen_t clilen;
pid_t pid;
int listenfd, connfd;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5566);
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
Bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
Listen(listenfd, 5);
while(1)
{
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
if ((pid = fork()) == 0)
{
while(1)
{
int nread = 0;
int nwrite = 0;
char readbuf[1024];
memset(readbuf, 0, sizeof(readbuf));
close(listenfd);
// nread = read(connfd, readbuf, sizeof(readbuf));
while((nread = read(connfd, readbuf, sizeof(readbuf))) < 0)
{
if (errno == EINTR)
continue;
perror("read");
}
if (nread == 0)
{
printf("client close\n");
exit(0);
}
printf("nread: %d\n", nread);
fputs(readbuf, stdout);
nwrite = write(connfd, readbuf, strlen(readbuf));
while(nwrite < 0)
{
if (errno == EINTR)
continue;
perror("write");
}
}
}
else if (pid < 0)
{
perror("fork");
exit(-1);
}
close(connfd);
}
return 0;
}
第一次连接通信时,我们可以看到,并没有产生僵尸进程。
当客户端结束通信,服务端主进程不退出,服务端子进程输出client close并exit(0)退出时,可以看到僵尸进程产生了。
服务端一直没有退出,当客户端发起第二次连接通信时,我们可以看到,之前产生的进程僵尸进程依然残留。
客户端第二次结束通信,又产生了一个僵尸进程。
僵尸进程产生的原因是,服务器子进程终止时,会给父进程发送一个SIGCHLD信号,此时父进程的默认的处理是忽略此信号,所以产生了僵尸进程,只有等到服务端完全关闭时,才会自动的回收僵尸进程,但是服务端通常是长时间的工作,如果每处理一个连接就会留下一个僵尸进程的话将会耗费大量资源,所以我们需要捕获子进程终止时的信号,让系统杀死僵尸进程。
此时我们引入一个waitpid函数
pid_t waitpid(pid_t pid, int* statloc, int options);
成功时返回进程pid(大于零)
此函数用于处理僵尸进程。
还需要一个捕获信号的函数,表明当捕获到SIGCHLD类型的信号时调用sig_chld函数。
signal(SIGCHLD, sig_chld);
信号处理函数我们需要在循环地调用waitpid,原因是防止在进入信号处理函数的时候收到另外多个SIGCHLD信号,然而只被捕获一次或两次信号,从而只能处理1-2次信号。WNOHANG参数可以避免有尚未终止子进程运行时阻塞,这也是这里不用wait的原因。
void sig_chld()
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
}
最终服务端的代码
#include
#include
#include
#include
#include
#include
#include
#include "socketio.h"
void sig_chld()
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
}
int main()
{
struct sockaddr_in servaddr, cliaddr;
socklen_t clilen;
pid_t pid;
int listenfd, connfd;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5566);
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
Bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
Listen(listenfd, 5);
signal(SIGCHLD, sig_chld);
while(1)
{
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
if ((pid = fork()) == 0)
{
while(1)
{
int nread = 0;
int nwrite = 0;
char readbuf[1024];
memset(readbuf, 0, sizeof(readbuf));
close(listenfd);
// nread = read(connfd, readbuf, sizeof(readbuf));
while((nread = read(connfd, readbuf, sizeof(readbuf))) < 0)
{
if (errno == EINTR)
continue;
perror("read");
}
if (nread == 0)
{
printf("client close\n");
exit(0);
}
fputs(readbuf, stdout);
nwrite = write(connfd, readbuf, strlen(readbuf));
while(nwrite < 0)
{
if (errno == EINTR)
continue;
perror("write");
}
}
}
else if (pid < 0)
{
perror("fork");
exit(-1);
}
close(connfd);
}
}
最终结果,客户端结束通信,服务端子进程结束,主进程仍然在运行时,不产生僵尸进程。