首页 > 代码库 > UNIX网络编程卷1 服务器程序设计范式5 预先派生子进程,由父进程向子进程传递套接字描述符

UNIX网络编程卷1 服务器程序设计范式5 预先派生子进程,由父进程向子进程传递套接字描述符

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie


1.只让你进程调用 accept,然后把所接受的已连接套接字“传递”给某个子进程。
这样做就不用因为所有子进程都调用 accept 而需提供上锁保护
2.父进程必须跟踪子进程的忙闲状态,以便给空闲子进程传递新的套接字


typedef struct {
  pid_t		child_pid;		/* 子进程的进程 ID */
  int		child_pipefd;	/* 父进程中连接到该子进程的字节流管道描述符 */
  int		child_status;	/* 子进程状态 */
  long		child_count;	/* 该子进程已处理的客户计数 */
} Child;


Child	*cptr;		/* array of Child structures; calloc'ed */


/* include serv05a */
static int		nchildren;


int
main(int argc, char **argv)
{
	int			listenfd, i, navail, maxfd, nsel, connfd, rc;//navail 表示可用子进程数目
	void		sig_int(int);
	pid_t		child_make(int, int, int);
	ssize_t		n;
	fd_set		rset, masterset;
	socklen_t	addrlen, clilen;
	struct sockaddr	*cliaddr;


	//0.创建监听套接字
	if (argc == 3)
		listenfd = Tcp_listen(NULL, argv[1], &addrlen);
	else if (argc == 4)
		listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
	else
		err_quit("usage: serv05 [ <host> ] <port#> <#children>");


	
	FD_ZERO(&masterset);
	FD_SET(listenfd, &masterset); //打开监听套接字的位
	maxfd = listenfd;
	cliaddr = Malloc(addrlen); //分配 Child 结构数组的内存空间


	//1.增设一个命令行参数供用户指定预先派生的子进程个数。
	nchildren = atoi(argv[argc-1]);
	navail = nchildren;
	cptr = Calloc(nchildren, sizeof(Child));


		/* 4prefork all the children */
	//2.调用 child_make 创建各个子进程
	for (i = 0; i < nchildren; i++) {
		child_make(i, listenfd, addrlen);	/* parent returns */
		FD_SET(cptr[i].child_pipefd, &masterset); //打开到子进程的字节流管理对应的位
		maxfd = max(maxfd, cptr[i].child_pipefd);
	}


	//3.设置中断信号 SIGINT 的处理函数
	Signal(SIGINT, sig_int);


	for ( ; ; ) {
		rset = masterset;
		//如果无可用子进程则关掉监听套接字
		if (navail <= 0)
			FD_CLR(listenfd, &rset);	/* turn off if no available children */
		//阻塞于 select 调用等待连接或与子进程的字节流管道可读
		nsel = Select(maxfd + 1, &rset, NULL, NULL, NULL);


			/* 4check for new connections */
		//4.accept 新连接
		if (FD_ISSET(listenfd, &rset)) {
			clilen = addrlen;
			//accept 新连接,得到已连接套接字描述符
			connfd = Accept(listenfd, cliaddr, &clilen);


			//找到第一个可用的子进程
			for (i = 0; i < nchildren; i++)
				if (cptr[i].child_status == 0)
					break;				/* available */


			if (i == nchildren)
				err_quit("no available children");
			cptr[i].child_status = 1;	/* 把该子进程的状态标记为 1 (busy) */
			cptr[i].child_count++; // 该子进程已处理的客户计数加 1
			navail--; //可用子进程数减 1


			//向该子进程的字节流管道传递一个单字节的数据
			n = Write_fd(cptr[i].child_pipefd, "", 1, connfd);
			Close(connfd); //关闭已连接套接字
			if (--nsel == 0) // 如果 select 返回的可读描述符已处理完,直接进入下一轮循环 
				continue;	/* all done with select() results */
		}


			/* 4find any newly-available children */
		//5.处理新近可用的子进程
		for (i = 0; i < nchildren; i++) {
			if (FD_ISSET(cptr[i].child_pipefd, &rset)) {
				if ( (n = Read(cptr[i].child_pipefd, &rc, 1)) == 0)
					err_quit("child %d terminated unexpectedly", i);
				cptr[i].child_status = 0; //把该子进程的状态标记为 可用
				navail++; //增加navail 计数器
				if (--nsel == 0) //如果 select 返回的可读描述符已处理完,break 出这个循环,进入外部循环的下一轮
					break;	/* all done with select() results */
			}
		}
	}
}
/* end serv05a */


//中断信号 SIGINT 处理函数
void
sig_int(int signo)
{
	int		i;
	void	pr_cpu_time(void);


		/* 4terminate all children */
	//给每个子进程发送 SIGTERM 信号终止它们
	for (i = 0; i < nchildren; i++)
		kill(cptr[i].child_pid, SIGTERM);
	//用 wait 回收所有子进程的资源
	while (wait(NULL) > 0)		/* wait for all children */
		;
	if (errno != ECHILD)
		err_sys("wait error");
		
	//调用 pr_cpu_time 统计已终止子进程的资源利用统计
	pr_cpu_time();


	//todo
	for (i = 0; i < nchildren; i++)
		printf("child %d, %ld connections\n", i, cptr[i].child_count);


	exit(0);
}




/* include child_make */
#include	"unp.h"
#include	"child.h"


pid_t
child_make(int i, int listenfd, int addrlen)
{
	int		sockfd[2];
	pid_t	pid;
	void	child_main(int, int, int);


	//1.创建一个字节流管道
	Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);


	//2.创建子进程
	if ( (pid = Fork()) > 0) {
		Close(sockfd[1]); //父进程关闭其中一个描述符 sockfd[1]
		cptr[i].child_pid = pid; //保存子进程 pid
		cptr[i].child_pipefd = sockfd[0]; // 父进程中连接到该子进程的字节流管道描述符
		cptr[i].child_status = 0; // 设置子进程的状态为 0 (ready)
		return(pid);		/* 父进程返回 */
	}


	Dup2(sockfd[1], STDERR_FILENO);		/* 子进程把流管道的自身拥有端复制到标准错误输出,这样每个子进程就通过读写标准错误输出和父进程通信 */
	Close(sockfd[0]);  //关闭流管道 sockfd[0] 
	Close(sockfd[1]);  //关闭流管道 sockfd[1] (sockfd[1]已经复制到标准错误输出了)
	Close(listenfd);   //子进程不用监听客户连接
	child_main(i, listenfd, addrlen);	/* never returns */
}
/* end child_make */


/* include child_main */
void
child_main(int i, int listenfd, int addrlen)
{
	char			c;
	int				connfd;
	ssize_t			n;
	void			web_child(int);


	printf("child %ld starting\n", (long) getpid());
	for ( ; ; ) {
		//1.等待来自父进程的已连接套接字描述符
		if ( (n = Read_fd(STDERR_FILENO, &c, 1, &connfd)) == 0)
			err_quit("read_fd returned 0");
		if (connfd < 0)
			err_quit("no descriptor from read_fd");


		//2.处理客户请求
		web_child(connfd);				/* process request */
		
		//3.关闭已连接套接字
		Close(connfd);


		//4.告诉父进程自己已准备好
		Write(STDERR_FILENO, "", 1);	/* tell parent we're ready again */
	}
}
/* end child_main */


UNIX网络编程卷1 服务器程序设计范式5 预先派生子进程,由父进程向子进程传递套接字描述符