Linux 网络编程详解七(并发僵尸进程处理)
在上一篇程序框架中,解决了子进程退出,父进程继续存在的功能,但是多条客户端连接如果同一时间并行退出,
导致服务器端多个子进程同一时间全部退出,而SIGCHLD是不可靠信号,同时来多条信号可能无法处理,导致出现僵尸进程,
如果使用while循环wait又会阻塞父进程,这里采取waitpid()函数来解决这个问题。
强调:后来经过实践,发现该方案在大并发时也有问题,例子中只有2个子进程,所以貌似可以解决问题,经过测试如果同时有上百个子进程同时退出,
仍然会出现SIGCHLD信号丢失情况。
因此批量kill子进程的时候必须确认kill成功之后再kill下一个子进程(可以使用kill -0 pid来确认)。
//辅助类实现 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <sys/wait.h> #include <signal.h> #include <netinet/in.h> #include <arpa/inet.h> #include "pub.h" ssize_t readn(int fd, const void *buf, ssize_t count) { if (buf == NULL) { printf("readn() params is not correct !\n"); return -1; } //定义剩余字节数 ssize_t lread = count; //定义辅助指针变量 char *pbuf = (char *) buf; //定义每次读取的字节数 ssize_t nread = 0; while (lread > 0) { nread = read(fd, pbuf, lread); if (nread == -1) { //read是可中断睡眠函数,需要屏蔽信号 if (errno == EINTR) continue; perror("read() err"); return -1; } else if (nread == 0) { printf("peer read socket is closed !\n"); //返回已经读取的字节数 return count - lread; } //重置剩余字节数 lread -= nread; //辅助指针后移 pbuf += nread; } return count; } ssize_t writen(int fd, const void *buf, ssize_t count) { if (buf == NULL) { printf("writen() params is not correct !\n"); return -1; } //定于剩余字节数 ssize_t lwrite = count; //定义每次写入字节数 ssize_t nwrite = 0; //定义辅助指针变量 char *pbuf = (char *) buf; while (lwrite > 0) { nwrite = write(fd, pbuf, lwrite); if (nwrite == -1) { if (errno == EINTR) continue; perror("write() err"); return -1; } else if (nwrite == 0) { printf("peer write socket is closed !\n"); return count - lwrite; } //重置剩余字节数 lwrite -= nwrite; //辅助指针变量后移 pbuf += nwrite; } return count; } ssize_t recv_peek(int fd, const void *buf, ssize_t count) { if (buf == NULL) { printf("recv_peek() params is not correct !\n"); return -1; } ssize_t ret = 0; while (1) { //此处有多少读取多少,不一定ret==count ret = recv(fd, (void *) buf, count, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } return -1; } ssize_t mreadline(int fd, const void *buf, ssize_t count) { //定义剩余字节数 ssize_t lread = count; //定义每次读取的字节数 ssize_t nread = 0; //定义辅助指针变量 char *pbuf = (char *) buf; int i = 0, ret = 0; while (1) { nread = recv_peek(fd, pbuf, count); if (nread == -1) { perror("recv_peek() err"); return -1; } else if (nread == 0) { //注意:这里一个客户端有两个进程,也就是套接字会关闭两次,会向服务器发送两次FIN信号 printf("peer socket is closed !\n"); return -1; } for (i = 0; i < nread; i++) { if (pbuf[i] == '\n') { //这是一段报文 memset(pbuf, 0, count); //从socket缓存区读取i+1个字节 ret = readn(fd, pbuf, i + 1); if (ret != i + 1) return -1; return ret; } } //如果当前socket缓存区中没有\n, //那么先判断自定义buf是否还有空间,如果没有空间,直接退出 //如果有空间,先将当前socket缓存区中的数据读出来,放入buf中,清空socket缓存 //继续recv,判断下一段报文有没有\n if (lread >= count) { printf("自定义buf太小了!\n"); return -1; } //读取当前socket缓存 ret = readn(fd, pbuf, nread); if (ret != nread) return -1; lread -= nread; pbuf += nread; } return -1; } void handler(int sign) { if (sign == SIGCHLD) { int mypid=0; //WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。 while((mypid=waitpid(-1,NULL,WNOHANG))>0) { printf("子进程pid=%d\n",mypid); } //wait(NULL); } } int server_socket() { int listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) { perror("socket() err"); return -1; } //reuseaddr int optval = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { perror("setsockopt() err"); return -1; } //bind struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { perror("bind() err"); return -1; } //listen if (listen(listenfd, SOMAXCONN) == -1) { perror("listen()err"); return -1; } pid_t pid = 0; //忽略SIGCHLD信号 //signal(SIGCHLD,SIG_IGN); //安装信号 if (signal(SIGCHLD, handler) == SIG_ERR) { printf("signal() failed !\n"); return -1; } while (1) { struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen); printf("accept by %s\n", inet_ntoa(peeraddr.sin_addr)); if (conn == -1) { perror("accept() err"); return -1; } pid = fork(); if (pid == -1) { perror("fork() err"); return -1; } //子进程接收数据 if (pid == 0) { //关闭监听套接字 close(listenfd); char buf[1024] = { 0 }; int ret = 0; while (1) { ret = mreadline(conn, buf, 1024); if (ret == -1) { close(conn); return -1; } //打印客户端数据 fputs(buf, stdout); //把数据返回给客户端 writen(conn, buf, ret); memset(buf, 0, sizeof(buf)); } } else if (pid > 0) { close(conn); } } return 0; } int client_say(int fd) { int ret = 0; char buf[1024] = { 0 }; while (fgets(buf, 1024, stdin) != NULL) { //发送数据 ret = writen(fd, buf, strlen(buf)); if (ret != strlen(buf)) return -1; memset(buf, 0, sizeof(buf)); ret = mreadline(fd, buf, sizeof(buf)); if (ret == -1) { return -1; } fputs(buf,stdout); memset(buf, 0, sizeof(buf)); } return 0; } int client_socket() { int sockarr[10] = { 0 }; int i = 0; //同时创建5个连接 for (i = 0; i < 5; i++) { sockarr[i] = socket(AF_INET, SOCK_STREAM, 0); if (sockarr[i] == -1) { perror("socket() err"); return -1; } //bind struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockarr[i], (struct sockaddr *) &addr, sizeof(addr)) == -1) { perror("connect() err"); return -1; } //获取本机地址 struct sockaddr_in myaddr; socklen_t mylen = sizeof(myaddr); if (getsockname(sockarr[i], (struct sockaddr *) &myaddr, &mylen) == -1) { perror("getsockname() err"); return -1; } printf("本次连接地址:%s\n",inet_ntoa(myaddr.sin_addr)); } client_say(sockarr[0]); return 0; }