UNIX网络编程——客户/服务器程序设计示范(三)
TCP预先派生子进程服务器程序,accept无上锁保护
我们的第一个“增强”型服务器程序使用称为预先派生子进程的技术。使用该技术的服务器不像传统意义的并发服务器那样为每个客户现场派生一个子进程,而是启动阶段预先派生一定数量的子进程,当各个客户连接到达时,这些子进程立即就能为他们服务。下图展示了服务器父进程预先派生出N个子进程且正有2个客户连接着的情形。
这种技术的优点在于无须引入父进程执行fork的开销就能处理新到的客户。缺点则是父进程在服务器启动阶段猜测需要预先派生多少子进程。如果某个时刻客户数恰好等于子进程总数,那么新到的客户将被“忽略”,直到至少有一个子进程重新可用。其实这些客户并未被完全忽略。内核将为每个新到的客户完成三路握手,直到达到相应套接字上listen调用backlog数为止,然后再服务器调用accept时把这些已完成的连接传递给它。这么一来客户就能觉察到服务器在响应时间上的恶化,因为尽管它的connect调用可能立即返回,但是它的第一个请求可能是在一段时间之后才被服务器处理。
通过增加一些代码,服务器总能应对客户负载的变动。父进程必须做的就是持续监视可用(即闲置)子进程数,一旦该值降到低于某个阈值就派生额外的子进程。同样,一旦该值超过另一个阈值就终止一些过剩的子进程,因为过多的可用子进程也会导致性能退化。不过在考虑这些增强之前,我们首先查看这类服务器程序的基本结构。如下给出预先派生子进程服务器程序第一版本的main函数。
#include "unp.h" static int nchildren;
static pid_t *pids; int
main(int argc, char **argv)
{
int listenfd, i;
socklen_t addrlen;
void sig_int(int);
pid_t child_make(int, int, int); 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: serv02 [ <host> ] <port#> <#children>");
nchildren = atoi(argv[argc-1]);
pids = Calloc(nchildren, sizeof(pid_t)); for (i = 0; i < nchildren; i++)
pids[i] = child_make(i, listenfd, addrlen); /* parent returns */ Signal(SIGINT, sig_int); for ( ; ; )
pause(); /* everything done by children */
} void
sig_int(int signo)
{
int i;
void pr_cpu_time(void); /* 4terminate all children */
for (i = 0; i < nchildren; i++)
kill(pids[i], SIGTERM);
while (wait(NULL) > 0) /* wait for all children */
;
if (errno != ECHILD)
err_sys("wait error"); pr_cpu_time();
exit(0);
}
14-21 增设一个命令行参数供用户指定预先派生的子进程的个数。分配一个存放各个子进程ID的数组,用于在父进程即将终止时由main函数终止所有子进程。
23-24 调用chid_make函数创建各个子进程。
38-42 既然getrusage汇报的是已终止子进程的资源利用统计,在调用pr_cpu_time之前就必须终止所有子进程。我们通过给每个子进程发送SIGTERM信号终止他们,并通过调用wait汇集所有子进程的资源利用统计。
#include "unp.h" pid_t
child_make(int i, int listenfd, int addrlen)
{
pid_t pid;
void child_main(int, int, int); if ( (pid = Fork()) > 0)
return(pid); /* parent */ child_main(i, listenfd, addrlen); /* never returns */
} void
child_main(int i, int listenfd, int addrlen)
{
int connfd;
void web_child(int);
socklen_t clilen;
struct sockaddr *cliaddr; cliaddr = Malloc(addrlen); printf("child %ld starting\n", (long) getpid());
for ( ; ; ) {
clilen = addrlen;
connfd = Accept(listenfd, cliaddr, &clilen); web_child(connfd); /* process the request */
Close(connfd);
}
}
9-12 调用fork派生子进程后只有父进程返回。子进程调用child_main函数,它是个无限循环。
26-32 每个子进程调用accept返回一个已连接的套接字,然后调用web_child处理客户请求,最后关闭连接。子进程一直在这个循环中反复,直到被父进程终止。
#include "unp.h" #define MAXN 16384 /* max # bytes client can request */ void
web_child(int sockfd)
{
int ntowrite;
ssize_t nread;
char line[MAXLINE], result[MAXN]; for ( ; ; ) {
if ( (nread = Readline(sockfd, line, MAXLINE)) == 0)
return; /* connection closed by other end */ /* 4line from client specifies #bytes to write back */
ntowrite = atol(line);
if ((ntowrite <= 0) || (ntowrite > MAXN))
err_quit("client request for %d bytes", ntowrite); Writen(sockfd, result, ntowrite);
}
}
select冲突
当多个进程在引用同一个套接字的描述符上调用select时就会发生冲突,因为在socket结构中为存放本套接字就绪之时应该唤醒哪些进程而分配的仅仅是一个进程ID的空间。如果有多个进程在等待同一个套接字,那么内核必须唤醒的是阻塞在select调用中的所有进程,因为它不知道哪些进程受到刚变得就绪的这个套接字影响。
我们可以迫使本服务器发送select冲突,办法是在调用accept之前加上一个select调用,等待监听套接字变为可读。各个子进程将阻塞在selec调用而不是accept调用中。如下给出了child_main函数的改动部分,不同于上面的若干行通过标以加号指出。如此修改后,通过检查BSD/OS内核的nselcoll计数器在服务器运行前后的变化,我们发现某此运行本服务器出现1814个冲突,下一个运行出现2045个冲突。既然两个客户为每次运行本服务器总共产生5000个连接,这两个结果相当于约有35%-40%的select调用引起冲突。
从以上讨论我们可以得出如下经验:如果有多个进程阻塞在引用同一个实体(例如套接字或普通文件,由file结构直接或间接描述)的描述符,那么最好直接阻塞在诸如accept之类的函数而不是select之中。
UNIX网络编程——客户/服务器程序设计示范(三)的更多相关文章
- UNIX网络编程——客户/服务器程序设计示范(总结)
(1)当系统负载较轻是,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了.这个模型甚至可以与inetd结合使用,也就是inetd处理每个连接的接收.我们的其他意见是就重负荷运 ...
- UNIX网络编程——客户/服务器程序设计示范(八)
TCP预先创建线程服务器程序,主线程统一accept 最后一个使用线程的服务器程序设计示范是在程序启动阶段创建一个线程池之后只让主线程调用accept并把每个客户连接传递给池中某个可用线程. ...
- UNIX网络编程——客户/服务器程序设计示范(七)
TCP预先创建线程服务器程序,每个线程各自accept 前面讨论过预先派生一个子进程池快于为每个客户线程派生一个子进程.在支持线程的系统上,我们有理由预期在服务器启动阶段预先创建一个线程池以取 ...
- UNIX网络编程——客户/服务器程序设计示范(六)
TCP并发服务器程序,每个客户一个线程 前面讲述了,每个客户一个进程的服务器,或为每个客户现场fork一个子进程,或者预先派生一定数目的子进程.如果服务器主机支持线程,我们就可以改用线程以取代子进程. ...
- UNIX网络编程——客户/服务器程序设计示范(五)
TCP预先派生子进程服务器程序,传递描述符 对预先派生子进程服务器程序的最后一个修改版本是只让父进程调用accept,然后把所接受的已连接套接字"传递"给某个子进程.这么做 ...
- UNIX网络编程——客户/服务器程序设计示范(二)
TCP并发服务器程序,每个客户一个子进程 传统上并发服务器调用fork派生一个子进程来处理每个客户.这使得服务器能够同时为多个客户服务,每个进程一个客户.客户数目的唯一限制是操作系统对以其名义 ...
- UNIX网络编程——客户/服务器程序设计示范(一)
下面给出的是客户程序用于测试我们的服务器程序的各个变体. #include "unp.h" #define MAXN 16384 /* max # bytes to request ...
- UNIX网络编程——客户/服务器程序设计示范(四)
TCP预先派生子进程服务器程序,accept使用线程上锁保护 我们使用线程上锁保护accept,因为这种方法不仅适用于同一进程内各线程之间的上锁,而且适用于不同进程之间的上锁. ...
- UNIX网络编程——客户/服务器心搏函数
阅读此博客时,可以参考以前的博客<<UNIX网络编程--socket的keep-alive>>和<<UNIX网络编程--套接字选项(心跳检测.绑定地址复用)> ...
随机推荐
- [bzoj4824][Cqoi2017]老C的键盘
来自FallDream的博客,未经允许,请勿转载,谢谢. 老 C 是个程序员. 作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序在某种 ...
- C语言程序设计第三次作业 —— 选择结构(1)
(一)改错题 计算f(x)的值:输入实数x,计算并输出下列分段函数f(x)的值,输出时保留1位小数. (错误一) 错误原因及改正:第九行语句结尾缺少半角分号,添加分号即可改正 (错误二) 错误原因及改 ...
- JAVA解析XML文件(DOM,SAX,JDOM,DOM4j附代码实现)
1.解析XML主要有四种方式 1.DOM方式解析XML(与平台无关,JAVA提供,一次性加载XML文件内容,形成树结构,不适用于大文件) 2.SAX方式解析XML(基于事件驱动,逐条解析,适用于只处理 ...
- 手把手教你全家桶之React(一)
前言 最近项目用到react,其实前年我就开始接触react,时光匆匆,一直没有时间整理下来(太懒啦)!如今再次用到,称工作间隙,对全家桶做一次总结,项目源码地址.废话不多说,上码. 创建一个文件目录 ...
- java.lang.NoSuchMethodException: org.apache.catalina.deploy.WebXml addServlet
解决此异常的方法是: 删除您添加在Referenced Libraries 下的catalina.jar包, 然后删除Webapp下的部署程序,重新部署后一切正常. ? --------------- ...
- Docker 工具和示例
pipework Jérôme Petazzoni 编写了一个叫 pipework 的 shell 脚本,可以帮助用户在比较复杂的场景中完成容器的连接. playground Brandon Rhod ...
- 2017-暑假作业-Java语言程序设计
任务列表 1.学会使用Markdown做笔记 本篇随笔就是使用的Markdown语法.养成做笔记的习惯! 参考资料: 极简MarkDown排版介绍(How to) stackedit:在线Markdo ...
- 微信小程序基础之开源项目库汇总
awesome-github-wechat-weapp 是由OpenDigg整理并维护的微信小程序开源项目库集合.我们会定期同步OpenDigg上的项目到这里,也欢迎各位提交项目给我们. (链接:ht ...
- Oracle导出表
方法一:利用PL/SQL Developer工具导出: 菜单栏-->Tools-->Export Tables,如下图,设置相关参数即可: 方法二:可以用cmd的操作命令导出,详情请去百度 ...
- 排查Full GC
我们的Java应用因频繁FULL GC导致性能降低很多,经过多人的定位也没有结论,于是我自主请命,经过一天的研究终于搞定了,现把经验与大家共享,相关的gc日志如下: 4.758: [Full GC [ ...