Linux 网络编程详解六(多进程服务器僵尸进程解决方案)
小结:在点对点p2p程序中,服务器端子程序退出,子进程会主动发送信号,关闭父进程,
但是这种模式导致服务器只能支持一个客户端连接,本章节中使用新的框架,子进程退出,不主动发送信号关闭父进程,而是父进程安装SIGCHLD信号,wait()子进程。
这样就支持了多客户端。
僵尸进程解决方案 1.忽略SIGCHLD信号,这样不会出现僵尸进程 2.安装信号,父进程接收到SIGCHLD信号后,wait()子进程
//头文件 int server_socket(); int client_socket();
//服务器端 #include "pub.h" int main(int arg,char *args[]) { server_socket(); return 0; }
//客户端 #include "pub.h" int main(int arg,char *args[]) { client_socket(); return 0; }
//辅助类实现 #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); printf("recv_peek() 执行!\n"); if (nread == -1) { perror("recv_peek() err"); return -1; } else if (nread == 0) { //注意:这里不要返回已经读取字节数,增加了调用函数的判断,直接返回-1,让调用函数当错误处理 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) { printf("子进程退出 !\n"); 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_socket() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -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(sockfd,(struct sockaddr *)&addr,sizeof(addr))==-1) { perror("connect() err"); return -1; } pid_t pid=0; pid=fork(); if(pid==-1) { perror("fork() err"); return -1; } char buf[1024]={0}; int ret=0; //子进程发送数据 if(pid==0) { while(fgets(buf,sizeof(buf),stdin)!=NULL) { ret=writen(sockfd,buf,strlen(buf)); if(ret!=strlen(buf)) return -1; memset(buf,0,sizeof(buf)); } }else if(pid>0) { //父进程接收数据 while(1) { ret=mreadline(sockfd,buf,sizeof(buf)); if(ret==-1) return -1; //打印数据 fputs(buf,stdout); memset(buf,0,sizeof(buf)); } } return 0; }
.SUFFIXES:.c .o CC=gcc SRCS=tec01.c\ pub.c OBJS=$(SRCS:.c=.o) EXEC=runc SRCS1=hello.c\ pub.c OBJS1=$(SRCS1:.c=.o) EXEC1=hello start:$(OBJS) $(OBJS1) $(CC) -o $(EXEC) $(OBJS) $(CC) -o $(EXEC1) $(OBJS1) @echo "-------OK----------" .c.o: $(CC) -Wall -g -o $@ -c $< clean: rm -f $(OBJS) rm -f $(EXEC) rm -f $(OBJS1) rm -f $(EXEC1)