linux网络多进程编程处理僵尸进程
2017-08-02 15:37
411 查看
前面的多进程程序中,当客户端关闭后,一个子进程会关闭,但是其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进程去处理,省去了大量僵尸进程占用系统资源。
但是使用wait会出现问题,wait是阻塞的,当多个子进程同时退出时,wait并不能等待所有的子进程退出,当它等待第一个子进程退出后,就返回了,这个时候就要用waitpid方法,如果只是waitpid并不能解决同时退出的问题,要使用while(waitpid()),保证回收所有退出的子进程。
什么是僵尸进程?
首先内核会释放终止进程(调用了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()),保证回收所有退出的子进程。
相关文章推荐
- Linux 网络编程详解六(多进程服务器僵尸进程解决方案)
- Linux C语言编程-Linux网络通信--Linux上使用套接字(socket)来处理信息---编写一个单进程非阻塞多客户的套接字客户端
- linux系统编程之进程(三):进程复制fork,孤儿进程,僵尸进程
- linux网络编程之socket:使用fork并发处理多个client的请求
- Linux 僵尸进程的处理
- linux下的僵尸进程处理SIGCHLD信号
- Linux进程理解与实践(四)wait函数处理僵尸进程
- Linux的僵尸进程处理1
- Linux 网络编程详解七(并发僵尸进程处理)
- Linux网络编程之进程、线程池及实现简单线程池
- Linux网络编程多进程模型
- 【Linux系统编程】特殊进程之僵尸进程
- Linux 下网络编程错误及处理
- Linux的僵尸进程处理2
- Linux系统编程——特殊进程之僵尸进程
- linux网络编程并发进程,select和epoll(一)
- 【网络编程小Tip】linux recvfrom延迟问题,导致处理时出现错误包
- linux下处理僵尸进程
- linux网络编程----->高并发--->多进程并发服务器
- Linux网络编程【三】:TCP服务器多进程和多线程(http访问)版本