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 */

时间: 2024-12-17 22:53:31

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

UNIX网络编程卷1 服务器程序设计范式4 预先派生子进程,以线程互斥锁上锁方式保护accept

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.文件上锁文件系统操作,比较耗时 2.线程上锁,不仅适用于同一进程内各线程之间的上锁,也适用于不同进程之间的上锁. 3.在不同进程之间使用线程上锁要求: 1)互斥锁变量必须存放在由所有进程共享的内存区中 2)必须告知线程函数库这是在不同进程之间共享的互斥锁 /* include my_lock_init */ #include "unpthread.h" #include &l

UNIX网络编程卷1 服务器程序设计范式8 预先创建线程,由主线程调用accept

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.程序启动阶段创建一个线程池之后只让主线程调用 accept 并把客户连接传递给池中某个可用线程. //用于维护关于每个线程基于信息的 Thread 结构 typedef struct { pthread_t thread_tid; /* 线程 ID */ long thread_count; /* 处理的连接数 */ } Thread; Thread *tptr; /* Thread

UNIX网络编程卷1 服务器程序设计范式7 预先创建线程,以互斥锁上锁方式保护accept

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.预先创建一个线程池,并让每个线程各自调用 accept 2.用互斥锁取代让每个线程都阻塞在 accept 调用之中的做法 //用于维护关于每个线程基于信息的 Thread 结构 typedef struct { pthread_t thread_tid; /* 线程 ID */ long thread_count; /* 处理的连接数 */ } Thread; Thread *tptr

UNIX网络编程卷1 服务器程序设计范式0 迭代服务器

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.迭代 TCP 服务器总是在完全处理某个客户的请求后才转向下一个客户. 2.从进程控制角度看迭代服务器是最快的,因为它不执行进程控制. /* include serv00 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; void sig_int(int), web_child

UNIX网络编程卷1 服务器程序设计范式1 并发服务器,为每个客户请求fork一个进程

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发服务器调用 fork 派生一个子进程来处理每个客户 2.传统并发服务器的问题在于为每个客户现场 fork 一个子进程比较耗费 CPU 时间. /* include serv01 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; void

UNIX网络编程卷1 服务器程序设计范式6 并发服务器,为每个客户请求创建一个线程

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.为每个客户请求创建一个线程,以取代为每个客户派生一个子进程 /* include serv06 */ #include "unpthread.h" int main(int argc, char **argv) { int listenfd, connfd; void sig_int(int); void *doit(void *); pthread_t tid; sockl

UNIX网络编程卷1 server程序设计范式8 预先创建线程,由主线程调用accept

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.程序启动阶段创建一个线程池之后仅仅让主线程调用 accept 并把客户连接传递给池中某个可用线程. //用于维护关于每一个线程基于信息的 Thread 结构 typedef struct { pthread_t thread_tid; /* 线程 ID */ long thread_count; /* 处理的连接数 */ } Thread; Thread *tptr; /* Threa

UNIX网络编程卷1 server程序设计范式1 并发server,为每一个客户请求fork一个进程

本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发server调用 fork 派生一个子进程来处理每一个客户 2.传统并发server的问题在于为每一个客户现场 fork 一个子进程比較耗费 CPU 时间. /* include serv01 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childp

UNIX网络编程卷1 回射服务器程序 TCP服务器程序设计范式 四个版本

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 这是一个简单的回射服务器程序.它将客户发送的数据读入缓冲区并回射其中内容 下面我会介绍同一个使用 TCP 协议的回射服务器程序的几个不同版本,分别是 fork 版本.select 版本.poll 版本.多线程版本 fork 版本:为每一个客户连接派生(fork) 一个子进程用来处理客户请求 /** * TCP/IPv4 协议相关 * **/ #include "unp.h" in