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

【网络】(五)避免僵尸进程

2015-09-13 11:10 525 查看
前面的四篇文章中,对于僵尸进程都只做了简单的处理,我忽略了子进程退出时发送给父进程的SIGCHLD信号,本篇文章来详细谈论一下僵尸进程!

1、为什么会有僵尸进程?

当父进程fork出的子进程运行结束后,为了父进程还能够获得子进程的一些信息,系统会维护子进程的一些信息,这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)。

如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程),继承这些子进程的init进程将清理它们(init进程将wait它们,从而去除僵尸状态)。

但通常情况下,不愿意保留僵尸进程,它们占用内核中的空间,最终可能导致我们耗尽进程资源。去除僵尸进程,可以从以下几个方面入手。

2、去除僵尸进程

(1) 忽略SIGCHLD信号

子进程在退出时会发送SIGCHLD信号给它的父进程,通知父进程它结束了,我们可在父进程中捕获这个信号,改变它的默认处理方式,直接忽略。这种方式简单粗暴!

(2)调用wait函数

调用wait函数等待子进程退出,也可去除子进程的僵尸状态,在某些情况下这种方法可用,但如果在同一时刻有多个子进程同时退出,那么在处理一个SIGCHLD信号的同时,另外的SIGCHLD也被发送到了父进程,该信号又是不可靠信号,所以就只会排队一个,最终导致的结果就是父进程处理所有SIGCHLD信号完毕后,还会存在僵尸进程!

(3)调用waitpid函数

调用waitpid函数可以安全的去除所有子进程的僵尸属性,这是一种推荐的做法!

示例代码:

基于前面几篇文章的服务器和客户端通信的代码,对客户端代码做一些更改,让它产生5个连接请求,这样服务端会产生5个子进程,当客户端退出时,服务端5个子进程都会退出成为僵尸进程,我们的目的就是处理这5个僵尸进程!

客户端完整代码

client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>

#define handle_error(msg)   \
do{perror(msg);exit(EXIT_FAILURE);}while(0)

ssize_t readn(int fd, void *buf, size_t count)
{
if((fd < 0) || (buf == NULL) || (count < 0))
return -1;
size_t nleft = count;   //剩余字节数
ssize_t nread = 0;      //已读字节数
char *pbuf = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd, pbuf, nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if (nread == 0)
return count - nleft;
pbuf += nread;
nleft -= nread;
}
return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
if((fd < 0) || (buf == NULL) || (count < 0))
return -1;
size_t nleft = count;   //剩余字节数
ssize_t nwritten = 0;      //已发送字节数
char *pbuf = (char*)buf;
while (nleft > 0)
{
if((nwritten = write(fd, pbuf, nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nwritten == 0)
continue;
pbuf += nwritten;
nleft -= nwritten;
}
return count;
}

//使用recv函数从套接字接收缓冲区中接收数据,但并不从缓冲去中清除数据
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while(true)
{
int iret = recv(sockfd, buf, len, MSG_PEEK);
if(iret == -1 && errno == EINTR)    //如果失败是因为信号中端,那么就重新再试
continue;
return iret;
}
}

ssize_t recvline(int sockfd, void *buf, size_t maxlen)
{
int iret = 0;
int nread = 0;  //已读数据
char *pbuf = (char*)buf;
int nleft = maxlen; //剩余字符
while(true)
{
iret = recv_peek(sockfd, pbuf, nleft);
if(iret < 0)
return iret;    //读取失败
else if(iret == 0)
return iret;    //对方关闭套接口

nread = iret;
if(nread > nleft)       //已经读取到的数据只可能小于或者等于剩余的数据
exit(EXIT_FAILURE);

for(int i = 0; i < nread; i++)
{
if(pbuf[i] == '\n')
{
iret = readn(sockfd, pbuf, i+1);    //从缓冲区中读走包括\n在内的数据
if(iret != i+1)
exit(EXIT_FAILURE);     //没有读取都i+1个数据,说明失败
return iret;                //读取都\n返回
}
}

//在当前读到的数据中没有发现\n,那么先将这部分数据从缓冲区中读走,然后接着偷窥后面的数据
nleft -= nread;
iret = readn(sockfd, pbuf, nread);
if(iret != nread)
exit(EXIT_FAILURE);
pbuf += nread;
}
return -1;
}

void client_handler(int sk_fd)
{
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
writen(sk_fd, sendbuf, strlen(sendbuf));   //发送数据

int iret = recvline(sk_fd, recvbuf, sizeof(recvbuf)); //接收包数据长度
if(iret == -1)
handle_error("read");
else if(iret == 0)
{
printf("Server was closed!\n");
break;
}
fputs(recvbuf, stdout);
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
}
}

int main(void)
{
int sk_fd[5];
for(int i = 0; i < 5; i++)
{
sk_fd[i] = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
if(sk_fd < 0)
handle_error("socket");
struct sockaddr_in sr_addr;
memset(&sr_addr,0,sizeof(sr_addr));
sr_addr.sin_family = AF_INET;
sr_addr.sin_port = htons(5188);
//sr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
sr_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_aton("127.0.0.1",&sr_addr.sin_addr);

if(connect(sk_fd[i], (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0)
{
close(sk_fd[i]);
handle_error("connect");
}
}

client_handler(sk_fd[0]);
for(int i = 0; i < 5; i++)
{
close(sk_fd[i]);
}
return 0;
}


服务端完整代码

server.c

编译命令:gcc -Wall -g -std=gnu99 server.c -o server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

#define handle_error(msg)   \
do{perror(msg);exit(EXIT_FAILURE);}while(0)

//忽略子进程的SIG_CHLD信号
void handle_SIGIGN(void)
{
struct sigaction act_chld;
act_chld.sa_handler = SIG_IGN;
act_chld.sa_flags = 0;
sigemptyset(&act_chld.sa_mask);

if(-1 == sigaction(SIGCHLD, &act_chld, NULL))//捕获终端中断信号
handle_error("sigaction SIGCHLD");
}

void wait_sigchld(int sig)
{
wait(NULL);
}
//调用wait函数处理该信号
void handle_wait(void)
{
struct sigaction act_chld;
act_chld.sa_handler = wait_sigchld;
act_chld.sa_flags = 0;
sigemptyset(&act_chld.sa_mask);

if(-1 == sigaction(SIGCHLD, &act_chld, NULL))//捕获终端中断信号
handle_error("sigaction SIGCHLD");
}

void waitpid_sigchld(int sig)
{
while( waitpid(-1,NULL,WNOHANG) >= 0); //不将执行挂起
}
//调用wait函数处理该信号
void handle_waitpid(void)
{
struct sigaction act_chld;
act_chld.sa_handler = waitpid_sigchld;
act_chld.sa_flags = 0;
sigemptyset(&act_chld.sa_mask);

if(-1 == sigaction(SIGCHLD, &act_chld, NULL))//捕获终端中断信号
handle_error("sigaction SIGCHLD");
}

ssize_t readn(int fd, void *buf, size_t count)
{
if((fd < 0) || (buf == NULL) || (count < 0))
return -1;
size_t nleft = count;   //剩余字节数
ssize_t nread = 0;      //已读字节数
char *pbuf = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd, pbuf, nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if (nread == 0)
return count - nleft;
pbuf += nread;
nleft -= nread;
}
return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
if((fd < 0) || (buf == NULL) || (count < 0))
return -1;
size_t nleft = count;   //剩余字节数
ssize_t nwritten = 0;      //已发送字节数
char *pbuf = (char*)buf;
while (nleft > 0)
{
if((nwritten = write(fd, pbuf, nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nwritten == 0)
continue;
pbuf += nwritten;
nleft -= nwritten;
}
return count;
}

//使用recv函数从套接字接收缓冲区中接收数据,但并不从缓冲去中清除数据
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while(true)
{
int iret = recv(sockfd, buf, len, MSG_PEEK);
if(iret == -1 && errno == EINTR)    //如果失败是因为信号中端,那么就重新再试
continue;
return iret;
}
}

//如果读到的数据包含\n,则返回,表示一条完整的消息读取完毕
ssize_t recvline(int sockfd, void *buf, size_t maxlen)
{
int iret = 0;
int nread = 0;  //已读数据
char *pbuf = (char*)buf;
int nleft = maxlen; //剩余字符
while(true)
{
iret = recv_peek(sockfd, pbuf, nleft);
if(iret < 0)
return iret;    //读取失败
else if(iret == 0)
return iret;    //对方关闭套接口

nread = iret;
if(nread > nleft)       //已经读取到的数据只可能小于或者等于剩余的数据
exit(EXIT_FAILURE);

for(int i = 0; i < nread; i++)
{
if(pbuf[i] == '\n')
{
iret = readn(sockfd, pbuf, i+1);    //从缓冲区中读走包括\n在内的数据
if(iret != i+1)
exit(EXIT_FAILURE);     //没有读取都i+1个数据,说明失败
return iret;                //读取都\n返回
}
}

//在当前读到的数据中没有发现\n,那么先将这部分数据从缓冲区中读走,然后接着偷窥后面的数据
nleft -= nread;
iret = readn(sockfd, pbuf, nread);
if(iret != nread)
exit(EXIT_FAILURE);
pbuf += nread;
}
return -1;
}

void do_work(int sock);

int main(void)
{
//三种僵尸进程处理方式
//handle_SIGIGN();
//handle_wait();
handle_waitpid();

int sk_fd = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
if(sk_fd < 0)
handle_error("socket");

//使用REUSEADDR,不必等待TIME_WAIT 状态消失,就可以重新使用端口
int on = 1;
if(setsockopt(sk_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
close(sk_fd);
handle_error("setsockopt");
}

struct sockaddr_in sr_addr;
memset(&sr_addr,0,sizeof(sr_addr));
sr_addr.sin_family = AF_INET;
sr_addr.sin_port = htons(5188);
sr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//sr_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_aton("127.0.0.1",&sr_addr.sin_addr);

if(bind(sk_fd, (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0)
{
close(sk_fd);
handle_error("bind");
}

//被动套接字
if(listen(sk_fd, SOMAXCONN) < 0)     //内核为此套接字排队的最大连接数由SOMAXCONN宏指定
{
close(sk_fd);
handle_error("listen");
}

struct sockaddr_in cl_addr;
socklen_t cl_length = sizeof(cl_addr);
pid_t pid;
while (true)
{
memset(&cl_addr,0,sizeof(cl_addr));
int ac_sk = accept(sk_fd, (struct sockaddr *)&cl_addr, &cl_length);
if(ac_sk < 0)
{
if(errno == EINTR)
continue;
close(sk_fd);
handle_error("accept");
}
printf("Connect ip = %s\tport = %d\n",inet_ntoa(cl_addr.sin_addr),ntohs(cl_addr.sin_port));

//每个客户端对应一个子进程
pid = fork();
if(pid == -1)
handle_error("fork");
if(pid == 0)
{
close(sk_fd);
do_work(ac_sk);
close(ac_sk);
exit(EXIT_SUCCESS);
}
else
close(ac_sk);
}
close(sk_fd);
return 0;
}

void do_work(int sock)
{
char recvbuf[1024];
while(true)
{
memset(recvbuf,0,sizeof(recvbuf));
int iret = recvline(sock,recvbuf,sizeof(recvbuf));    //获取包数据长度
if(iret == -1)
handle_error("read");
else if(iret == 0)
{
printf("Client was closed!\n");
break;
}
fputs(recvbuf,stdout);
writen(sock, recvbuf, strlen(recvbuf));    //回传数据
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: