linux网络多进程编程处理僵尸进程

来源:互联网 发布:spss数据 excel 编辑:程序博客网 时间:2024/05/19 18:46

前面的多进程程序中,当客户端关闭后,一个子进程会关闭,但是其pdb仍然存在,通过ps -ef|grep server可以查看到,对于服务器来说,大量的僵尸进程会消耗进程控制块数目,影响服务器性能,因此僵尸进程必须处理。

什么是僵尸进程?

首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。

而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。

任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。

僵尸进程的目的?

设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。

如何避免僵尸进程?

通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

#include<unistd.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<sys/wait.h>#include<stdlib.h>#include<stdio.h>#include<errno.h>#include<string.h>#include<signal.h>#define ERR_EXIT(m) do{perror(m);exit(EXIT_FAILURE);}while(1)struct packet{int len;char buf[1024];};ssize_t readn(int fd, void *buf, size_t count){size_t nleft = count;ssize_t nread;char *bufp = (char*)buf;while (nleft > 0){if ((nread = read(fd, bufp, nleft)) < 0){if (errno == EINTR){continue;return -1;}else if (nread == 0)return count - nleft;}bufp = bufp + nread;nleft = nleft - nread;}return count;}ssize_t writen(int fd, void *buf, size_t count){size_t nleft = count;ssize_t nwrite;char *bufp = (char*)buf;while (nleft > 0){if ((nwrite = write(fd, bufp, nleft)) < 0){if (errno == EINTR){continue;return -1;}else if (nwrite == 0)continue;}bufp = bufp + nwrite;nleft = nleft - nwrite;}return count;}ssize_t recv_peek(int sockfd, void *buf, size_t len){while (1){int ret = recv(sockfd, buf, len, MSG_PEEK);if (ret == -1 && errno == EINTR)continue;return ret;}}ssize_t readline(int sockfd, void *buf, size_t maxline){int ret;int nread;char *bufp = (char*)buf;int nleft = maxline;while (1){ret = recv_peek(sockfd, bufp, nleft);if (ret < 0)return ret;else if (ret == 0)return ret;//对方关闭了套接口nread = ret;for (int i = 0; i < nread; i++){if (bufp[i] == '\n'){ret = readn(sockfd, bufp, i + 1);//将缓冲区数据移除if (ret != i + 1)exit(EXIT_FAILURE);return ret;}}if (nread>nleft)exit(EXIT_FAILURE);//读到的数据不能超过缓冲区nleft -= nread;ret = readn(sockfd, bufp, nread);if (ret != nread)exit(EXIT_FAILURE);bufp += nread;}return -1;}void do_service(int connfd){char recvbuf[1024];int n;while (1){memset(&recvbuf, 0, sizeof(recvbuf));int ret = readline(connfd, recvbuf, 1204);if (ret == -1){ERR_EXIT("read");}else if (ret == 0){printf("client cloase\n");break;}fputs(recvbuf, stdout);writen(connfd, recvbuf, strlen(recvbuf));}}void handle_sigchild(int sig){//  wait(NULL);while (waitpid(-1, NULL, WNOHANG) > 0);//回收所有子进程}int main(void){//signal(SIGCHLD, SIG_IGN);signal(SIGCHLD,handle_sigchild);        int listenfd;if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)ERR_EXIT("socket");struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(5188);servaddr.sin_addr.s_addr = htonl(INADDR_ANY);int on = 1;if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)ERR_EXIT("setsockopt");if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)ERR_EXIT("bind");if (listen(listenfd, SOMAXCONN) < 0)ERR_EXIT("listten");while (1){int connfd;struct sockaddr_in peeraddr;socklen_t peerlen = sizeof(peeraddr);if ((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)ERR_EXIT("accept");printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));pid_t pid = fork();if (pid == -1)ERR_EXIT("fork");if (pid == 0){close(listenfd);do_service(connfd);exit(EXIT_SUCCESS);}if (pid > 0){close(connfd);}}close(listenfd);}

但是使用wait会出现问题,wait是阻塞的,当多个子进程同时退出时,wait并不能等待所有的子进程退出,当它等待第一个子进程退出后,就返回了,这个时候就要用waitpid方法,如果只是waitpid并不能解决同时退出的问题,要使用while(waitpid()),保证回收所有退出的子进程。

 
原创粉丝点击