【版权声明:转载请保留出处:blog.csdn.net/gentleliu。邮箱:shallnew*163.com】
上一节说到我们不愿意留存僵死进程,它会占用内核空间,最终可能导致资源耗尽。所以我们无论何时fork子进程时都要wait他们,以防止变成僵死进程。因此我们建立一个处理SIGCHLD信号的处理函数。
我们可以调用wait和waitpid函数来处理已经终止的子进程:
#include<sys/types.h> #include<sys/wait.h> pid_t wait (int * status); pid_t waitpid(pid_t pid,int * status,int options);
在调用wait的进程没有已经终止的子进程时,但有一个或多个子进程在运行时,那么wait函数将阻塞到现有子进程第一个终止为止。
今天我们来点复杂的例子---计算服务器。(哇噻!好复杂,该不会是大型并行计算服务器吧?)别着急,是这样的,我们让客户端从标准输入输入一个数字然后发送到服务器,服务器收到之后计算出这个数字的平方后在返回给客户端。
就这样子啦!(什么?这个啊?我还以为。。。)其实我是想用简单的例子来说明要讲的问题,这样重点更为突出,更易让人明白,因为我们不是讲某个具体大型服务器编程,在讲基础性的东东。本来用简单的例子就可以说明问题干吗还要整的那么复杂吗?搞的别人把时间都花在复杂的例子上面,到最后捡了芝麻丢了西瓜,一不小心又废话了!!
修改服务器程序如下:
#include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/wait.h> #include <signal.h> #include "server.h" #include "ly_debug.h" void accept_handle(int connfd, struct sockaddr_in peeraddr); void sig_chld(); int main(int argc, const char *argv[]) { int sockfd, connfd; struct sockaddr_in seraddr, peeraddr; socklen_t addrlen; COMPILE_TIME_PRINT; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); return -1; } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { LY_ERR("socket: %s\n", strerror(errno)); return -1; } seraddr.sin_family = AF_INET; seraddr.sin_port = ntohs(atoi(argv[1])); seraddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr))) { LY_ERR("bind: %s\n", strerror(errno)); return -1; } if (listen(sockfd, MAX_CONN) < 0) { LY_ERR("listen: %s\n", strerror(errno)); return -1; } addrlen = sizeof(struct sockaddr); for (;;) { connfd = accept(sockfd, (struct sockaddr *)(&peeraddr), &addrlen); if (connfd < 0) { LY_ERR("accept: %s\n", strerror(errno)); continue; } if (0 == fork()) { close(sockfd); accept_handle(connfd, peeraddr); close(connfd); exit(0); } close(connfd); printf("Child %d terminated\n", wait(NULL)); } return 0; } void accept_handle(int connfd, struct sockaddr_in peeraddr) { char buf[32]; int val; LY_IFO("Receive request from %s, port %d\n", inet_ntop(AF_INET, &peeraddr.sin_addr, buf, sizeof(buf)), ntohs(peeraddr.sin_port)); memset(buf, 0, sizeof(buf)); if (recv(connfd, buf, sizeof(buf), 0) < 0) { LY_ERR("recv: %s\n", strerror(errno)); return; } val = atoi(buf); val *= val; snprintf(buf, sizeof(buf), "%d", val); if (send(connfd, buf, sizeof(buf), 0) < 0) { LY_ERR("send: %s\n", strerror(errno)); return; } }
#include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include "ly_debug.h" int main(int argc, const char *argv[]) { int sockfd, ret; struct sockaddr_in seraddr; char buf[32]; COMPILE_TIME_PRINT; if (argc != 3) { LY_PRT("Usage: %s <ip> <port>\n", argv[0]); return -1; } if (inet_pton(AF_INET, argv[1], &seraddr.sin_addr) < 0) { LY_ERR("inet_pton: %s\n", strerror(errno)); return -1; } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { LY_ERR("socket: %s\n", strerror(errno)); return -1; } seraddr.sin_family = AF_INET; seraddr.sin_port = htons(atoi(argv[2])); if (connect(sockfd, (struct sockaddr *)(&seraddr), sizeof(struct sockaddr)) < 0) { LY_ERR("connect: %s\n", strerror(errno)); return -1; } fprintf(stdout, "Input a number: "); if (NULL == fgets(buf, sizeof(buf), stdin)) { LY_ERR("fgets failed!\n"); return -1; } if (send(sockfd, buf, sizeof(buf), 0) < 0) { LY_ERR("send: %s\n", strerror(errno)); } memset(buf, 0, sizeof(buf)); ret = recv(sockfd, buf, sizeof(buf), 0); //ret = read(sockfd, buf, sizeof(buf)); if (ret < 0) { LY_ERR("recv: %s\n", strerror(errno)); return -1; } else if (ret > 0) { LY_PRT("Result: %s\n", buf); } else { LY_PRT("Peer shutdown!\n"); } close(sockfd); return 0; }
那怎么办呢?别着急,我们还有个waitpid函数呢,那这个函数和wait函数有什么不同呢,这里关键在waitpid的第三个参数。先说第一个参数,pid是允许我们指定想等待的子进程ID ,-1表示等待第一个终止的进程。第三个参数Options允许我们指定附加选项,最常用的是WNOHANG,他告知内核在没有终止子进程时不要阻塞,那这不正是我们需要的吗?于是立马改代码,将服务器中for循环修改如下:
for (;;) { connfd = accept(sockfd, (struct sockaddr *)(&peeraddr), &addrlen); if (connfd < 0) { LY_ERR("accept: %s\n", strerror(errno)); continue; } if (0 == fork()) { close(sockfd); accept_handle(connfd, peeraddr); close(connfd); exit(0); } close(connfd); //printf("Child %d terminated\n", wait(NULL)); printf("Child %d terminated\n", waitpid(-1, NULL, WNOHANG)); }如上编译运行, 马上发现结果不对,服务器由于未等待子进程终止直接返回导致当子进程退出时父进程未处理子进程而导致僵尸进程出现了,如下图:
所以,我们专门最好写个函数来处理SIGCHLD 信号,这个函数如下:
void sig_chld() { pid_t pid; while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { printf("Child %d terminated\n", pid); } }
#include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/wait.h> #include <signal.h> #include "server.h" #include "ly_debug.h" void accept_handle(int connfd, struct sockaddr_in peeraddr); void sig_chld(); int main(int argc, const char *argv[]) { int sockfd, connfd; struct sockaddr_in seraddr, peeraddr; socklen_t addrlen; COMPILE_TIME_PRINT; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); return -1; } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { LY_ERR("socket: %s\n", strerror(errno)); return -1; } seraddr.sin_family = AF_INET; seraddr.sin_port = ntohs(atoi(argv[1])); seraddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr))) { LY_ERR("bind: %s\n", strerror(errno)); return -1; } if (listen(sockfd, MAX_CONN) < 0) { LY_ERR("listen: %s\n", strerror(errno)); return -1; } signal(SIGCHLD, sig_chld); addrlen = sizeof(struct sockaddr); for (;;) { connfd = accept(sockfd, (struct sockaddr *)(&peeraddr), &addrlen); if (connfd < 0) { LY_ERR("accept: %s\n", strerror(errno)); continue; } if (0 == fork()) { close(sockfd); accept_handle(connfd, peeraddr); close(connfd); exit(0); } close(connfd); //printf("Child %d terminated\n", wait(NULL)); //printf("Child %d terminated\n", waitpid(-1, NULL, WNOHANG)); } return 0; } void accept_handle(int connfd, struct sockaddr_in peeraddr) { char buf[32]; int val; LY_IFO("Receive request from %s, port %d\n", inet_ntop(AF_INET, &peeraddr.sin_addr, buf, sizeof(buf)), ntohs(peeraddr.sin_port)); memset(buf, 0, sizeof(buf)); if (recv(connfd, buf, sizeof(buf), 0) < 0) { LY_ERR("recv: %s\n", strerror(errno)); return; } val = atoi(buf); val *= val; snprintf(buf, sizeof(buf), "%d", val); if (send(connfd, buf, sizeof(buf), 0) < 0) { LY_ERR("send: %s\n", strerror(errno)); return; } } void sig_chld() { pid_t pid; while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { printf("Child %d terminated\n", pid); } }
下面我们做这样一个测试,运行一个服务器,然后运行一个客户端,客户端阻塞与标准输入,此时kill掉服务进程,看看会发生什么。发现有不对头的地方,有木有啊!
服务器已经被kill之后,客户端由于阻塞与标准输入,不能马上知道,只有当客户端输入后回车才发现对端已经不在了。这个问题涉及到i/o复用,在下一节我们继续。