9.僵尸进程的处理

TCP是一个流协议

       TCP是基于字节流传输的,只维护发送出去多少,确认了多少,没有维护消息与消息之间的边界,因而导致粘包问题.

        粘包的解决方法是在应用层维护消息边界.

僵尸进程与SIGCHLD信号

          signal(SIGCHLD,SIG_IGN);///  忽略SIGCHLD信号

         signal(SIGCHLD,handle_sigchld);


   5个客户端一起向服务器发送连接请求

这是客户端程序,服务器程序,没有发生变换


/// echolic.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

#define ERR_EXIT(m) \
		do{ \
			perror(m); \
			exit(EXIT_FAILURE); \
		}while(0)


ssize_t readn(int fd,void *buf,size_t count)
{
	size_t nleft = count ; // 未读取的数据
	ssize_t nread;// 已读取的数据
	char *bufp= (char*)buf;
	while(nleft > 0)
	{
		if( (nread = read(fd,bufp,nleft)) < 0)
		{
			if( errno == EINTR)
				 nread = 0;//  继续读取数据
			else
				return -1;
		}
		else if( nread == 0) // 对方关闭或已经读到eof
			break;
		bufp +=nread;
		nleft -= nread;
	
	}
	return count-nleft;
}

ssize_t writen(int fd,const void *buf,size_t count)
{
	size_t nleft=count;  // 未读取的
	ssize_t nwritten;    // 已读取的
 	char *bufp = (char*)buf;
 	
 	while(nleft > 0)
 	{
 		if((nwritten = write(fd,bufp,nleft)) < 0)
 		{
 			if( errno == EINTR)
 				continue;
 			else
 				return -1;
 		}
 		else if( nwritten == 0)
 			continue;
 		bufp  += nwritten;
 		nleft -= nwritten;
 	}
 	return count;
}


/// recv()只能读取套接字,而不能读取一般文件描述符
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
	while(1)
	{
		int ret = recv(sockfd,buf,len,MSG_PEEK);//MSG_PEEK接收缓冲区的数据,但是并没有清除
		if( ret == -1 && errno == EINTR)
			continue;
		return ret;
	}
}

// 读到'\n' 就返回,加上'\n'一行最多为maxline个字符
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
	int ret;
	int nread;
	char *bufp =(char *) buf;
	int nleft = maxline;
	//int count=0;
	
	while(1)
	{	
		// recv_peek读取缓冲区的字符个数,并放入到bufp缓存里面
		ret = recv_peek(sockfd,bufp,nleft);
		if(ret < 0)
			return ret;// 表示失败
		else if(ret == 0)
			return ret; // 表示对方关闭连接了
			
		nread = ret;
		// 判断接收到字符是否有'\n'
		int i;
		for(i=0; i<nread; ++i)
		{
			if(bufp[i] == '\n')
			{
			    // readn读取数据,这部分缓冲会被清空的
				ret = readn(sockfd,bufp,i+1);
				if(ret != (i+1))
					exit(EXIT_FAILURE);
				return ret; //+ count;
			}
		}
		if( nread > nleft)
			exit(EXIT_FAILURE);
		nleft -= nread;
		ret = readn(sockfd,bufp,nread);
		if(ret != nread)
			exit(EXIT_FAILURE);
		bufp += nread;// 下一次指针偏移	
		//count += nread;
	}
	return -1;
}

void echo_cli(int sock)
{
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while( fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)  
	{
		writen(sock,sendbuf,strlen(sendbuf));// 需要的注意的的时sizeof(sendbuf)和strlen(recvbuf)不一样的,容易出现混淆
		int ret = readline(sock,recvbuf,1024);///最后一个参数为缓冲区的最大值
		if( ret == -1 )
			ERR_EXIT("readline");
		else if(ret == 0)
		{
			printf("client close \n");
			break;
		}
		fputs(recvbuf,stdout);
		memset(recvbuf,0,sizeof(recvbuf));
		memset(sendbuf,0,sizeof(sendbuf));
	}
    
    close(sock); 	
}


int main()
{
	int sock[5];
	int i;
	for( i=0;i<5;++i)
	{
		if( (sock[i] = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
				ERR_EXIT("sock err");
		struct sockaddr_in servaddr;
		memset(&servaddr,0,sizeof(servaddr));
		
		servaddr.sin_family = AF_INET;
		servaddr.sin_port = htons(5188);
		servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
	
		if( connect(sock[i],(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
				ERR_EXIT("connect error");
	
		struct sockaddr_in localaddr;
		socklen_t addrlen = sizeof(localaddr);
		if(getsockname(sock[i],(struct sockaddr*)&localaddr,&addrlen) < 0)
			ERR_EXIT("getsockname err");
		printf("ip=%s ,port = %d \n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
	}
	// 为了方便,只使用一个套接字通信
	echo_cli(sock[0]);
      
	return 0;
}




   没有处理子进程,结果出现了好多5个僵尸进程.

最简单的办法就是父进程直接忽略SIGCHLD信号,即:

              signal(SIGCHLD,SIG_IGN);  // 忽略SIGCHLD信号

                   忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源


                           如果我们想要捕获SIGCHLD信号的话,在信号处理函数中不能只调用一次wait/waitpid 函数,因为客户端退出发出FIN段的时机是不一定的,如果都能按一定时间顺序发送给5个服务器子进程,即子进程发生SIGCHLD信号给父进程的时间有前后之分,那handler函数会被调用多次,则是允许的,也不会产生僵尸进程;但当多个SIGCHLD信号同时到达,因为不可靠信号不能排队导致信号只保存一个,即其余信号会丢失,则产生的僵尸进程个数是不确定的,因为按前面所说取决于5个SIGCHLD信号到达的次序。解决的办法很简单,只要在handler函数中while 循环一下就ok 了,即使5个信号同时到达,只要接收到一个SIGCHLD信号,则5个子进程都会被清理掉:

void handler(int sig)
{
    /*  wait(NULL); //只能等待第一个退出的子进程 */
   
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}

signal(SIGCHLD, handler);

           前几个程序,都没有注意到僵尸进程,平时的记得处理僵尸进程.













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值