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

时间:2021-12-19 18:26:42

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


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


typedef struct {
pid_tchild_pid;/* 子进程的进程 ID */
intchild_pipefd;/* 父进程中连接到该子进程的字节流管道描述符 */
intchild_status;/* 子进程状态 */
longchild_count;/* 该子进程已处理的客户计数 */
} Child;


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


/* include serv05a */
static intnchildren;


int
main(int argc, char **argv)
{
intlistenfd, i, navail, maxfd, nsel, connfd, rc;//navail 表示可用子进程数目
voidsig_int(int);
pid_tchild_make(int, int, int);
ssize_tn;
fd_setrset, masterset;
socklen_taddrlen, 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)
{
inti;
voidpr_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)
{
intsockfd[2];
pid_tpid;
voidchild_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)
{
charc;
intconnfd;
ssize_tn;
voidweb_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 */