【网络】(五)避免僵尸进程
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
服务端完整代码
server.c
编译命令:gcc -Wall -g -std=gnu99 server.c -o server
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)); //回传数据 } }
相关文章推荐
- Android OkHttp完全解析 是时候来了解OkHttp了
- Http 协议,WebSocket 协议
- HTTP Content-type
- HTTP缓存相关头
- 第三十六天 网络连接、单线程、多线程下载
- 通过NodeJS,Express搭建本地HTTP访问服务.
- TCP/IP协议三次握手与四次握手流程解析
- Android的HttpURLConnection总结
- IOS开发指南学习——使用MKNetworkKit进行网络请求
- 终极二进制分析TCP协议通信的过程和字段含义
- ARP实例分析-wireshark捕获的网络报文,二进制解析每个bit位对应的意思
- http://www.cnblogs.com/xia520pi/archive/2012/06/04/2534533.html(重要)
- Linux C语言程序设计(十八)——基于TCP的网络编程
- HTTP协议报文、工作原理及Java中的HTTP通信技术详解
- ipv6现状,加英文的中括号访问, ipv6测试http://test-ipv6.com
- ipv6现状,加英文的中括号访问, ipv6测试http://test-ipv6.com
- HDU 5012 Dice (2014年西安赛区网络赛F题)
- 网络请求配置
- 【转载】HTTP 头部解释
- HTTP响应状态码大全