在运行TCP并发服务器端代码时发现客户端退出时服务器端子进程产生了僵尸进程
服务器端避免僵尸进程的方法
通过忽略SIGCHLD信号,解决僵尸进程signal(SIGCHLD, SIG_IGN)。
通过wait方法,解决僵尸进程signal(SIGCHLD, handle_sigchld);wait(NULL)。
通过waitpid方法,解决僵尸进程signal(SIGCHLD, handle_sigchld);wait(-1,NULL, WNOHANG)。
此时我们通过添加信号函数修改原服务器端代码
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> #include<signal.h> void handler(int signo) { printf("child exit signal num is %d\n",signo); wait(NULL); } int main(int argc,char * argv[]) { pid_t pid; int listenfd,connfd; struct sockaddr_in ser,clt; char sendbuf[100]= {"success"},recvbuf[100]; socklen_t len=sizeof(clt); listenfd=socket(AF_INET,SOCK_STREAM,0); ser.sin_family = AF_INET; ser.sin_port = htons(8001); ser.sin_addr.s_addr = inet_addr("127.0.0.1"); int optval=1; signal(SIGCHLD,handler); if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))< 0) { printf("setsockopt bind error!\n"); exit(0); } if(bind(listenfd,(struct sockaddr*)&ser,sizeof(ser))<0) { printf("bind failed!\n"); exit(0); } if(listen(listenfd,SOMAXCONN)<0) { printf("listen failed\n"); exit(0); } for(;;) { connfd=accept(listenfd,(struct sockaddr*)&clt,(socklen_t *)&len); if(connfd==-1) { printf("connect failed\n"); exit(0); } pid=fork(); if(pid==0) { close(listenfd); for(;;) { int ret=read(connfd,recvbuf,sizeof(recvbuf)); printf("serv recvmsg:%s\n",recvbuf); if(ret==0) { close(connfd); exit(0); } write(connfd,sendbuf,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } } else { close(connfd); } } if(pid==0) { close(connfd); } else { close(listenfd); } return 0; }
再次查看进程状态,此时不会再出现僵尸进程
但是由于SIGCHLD是不可靠信号,在使用wait函数时不足以防止出现僵死进程,当五个连接同时发出断开请求时。
所有打开的描述符由内核自动关闭,5个连接同时终止,发送5个FIN,导致5个SIGCHLD信号产生。
建立了5个连接的TCP客户端程序
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> int main(int argc,char *argv[]) { struct sockaddr_in user; int sockfd[10],i; char sendbuf[100],recvbuf[100]; user.sin_family = AF_INET; user.sin_port = htons(8001); user.sin_addr.s_addr = inet_addr("127.0.0.1"); for(i=0; i<5; i++) { sockfd[i]=socket(AF_INET,SOCK_STREAM,0); if(sockfd[i]==-1) { printf("socket failed!\n"); exit(0); } if ( connect(sockfd[i], (struct sockaddr*) (&user), sizeof(user)) < 0) { perror("connect failed!\n"); exit(0); } } while(~scanf("%s",sendbuf)) { write(sockfd[4],sendbuf,sizeof(sendbuf)); read(sockfd[4],recvbuf,sizeof(recvbuf)); printf("client get msg:%s",recvbuf); } return 0; }
可以看出同时断开时只接受到了三个子进程退出信号,并且伴随僵尸进程的产生
所以应该使用waitpid(-1,NULL, WNOHANG)代替wait(NULL),不再有僵尸进程产生
void handler(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0) { } }
SIG_PIPE信号
如果对方socket已关闭,对等方再发写数据,则会产生SIGPIPE信号;SIGPIPE信号会让进程终止。往一个已经接收FIN的套接中写是允许的,接收到FIN仅仅代表对方不再发送数据。在收到RST段之后,如果再调用write就会产生SIGPIPE信号,对于这个信号的处理我们通常忽略即可。
signal(SIGPIPE, SIG_IGN);
close与shutdown区别
close终止了数据传送的两个方向。shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向。shutdown how=1就可以保证对等方接收到一个EOF字符,而不管其他进程是否已经打开了套接字。而close不能保证,直到套接字引用计数减为0时才发送。也就是说直到所有的进程都关闭了套接字。
#include <sys/socket.h> int shutdown(int sockfd, int how);