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网络编程——客户/服务器程序设计示范(三)的更多相关文章

  1. UNIX网络编程——客户/服务器程序设计示范(总结)

    (1)当系统负载较轻是,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了.这个模型甚至可以与inetd结合使用,也就是inetd处理每个连接的接收.我们的其他意见是就重负荷运 ...

  2. UNIX网络编程——客户/服务器程序设计示范(八)

        TCP预先创建线程服务器程序,主线程统一accept 最后一个使用线程的服务器程序设计示范是在程序启动阶段创建一个线程池之后只让主线程调用accept并把每个客户连接传递给池中某个可用线程.  ...

  3. UNIX网络编程——客户/服务器程序设计示范(七)

        TCP预先创建线程服务器程序,每个线程各自accept 前面讨论过预先派生一个子进程池快于为每个客户线程派生一个子进程.在支持线程的系统上,我们有理由预期在服务器启动阶段预先创建一个线程池以取 ...

  4. UNIX网络编程——客户/服务器程序设计示范(六)

    TCP并发服务器程序,每个客户一个线程 前面讲述了,每个客户一个进程的服务器,或为每个客户现场fork一个子进程,或者预先派生一定数目的子进程.如果服务器主机支持线程,我们就可以改用线程以取代子进程. ...

  5. UNIX网络编程——客户/服务器程序设计示范(五)

        TCP预先派生子进程服务器程序,传递描述符 对预先派生子进程服务器程序的最后一个修改版本是只让父进程调用accept,然后把所接受的已连接套接字"传递"给某个子进程.这么做 ...

  6. UNIX网络编程——客户/服务器程序设计示范(二)

        TCP并发服务器程序,每个客户一个子进程 传统上并发服务器调用fork派生一个子进程来处理每个客户.这使得服务器能够同时为多个客户服务,每个进程一个客户.客户数目的唯一限制是操作系统对以其名义 ...

  7. UNIX网络编程——客户/服务器程序设计示范(一)

    下面给出的是客户程序用于测试我们的服务器程序的各个变体. #include "unp.h" #define MAXN 16384 /* max # bytes to request ...

  8. UNIX网络编程——客户/服务器程序设计示范(四)

        TCP预先派生子进程服务器程序,accept使用线程上锁保护 我们使用线程上锁保护accept,因为这种方法不仅适用于同一进程内各线程之间的上锁,而且适用于不同进程之间的上锁.        ...

  9. UNIX网络编程——客户/服务器心搏函数

    阅读此博客时,可以参考以前的博客<<UNIX网络编程--socket的keep-alive>>和<<UNIX网络编程--套接字选项(心跳检测.绑定地址复用)> ...

随机推荐

  1. 【完整项目】使用Scrapy模拟HTTP POST,获取完美名字

    1. 背景 最近有人委托我给小孩起个名字,说名字最好符合周易五行生克理论,然后给了我个网址,说像是这个网站中的八字测名,输入名字和生辰八字等信息,会给出来这个名字的分数和对未来人生的预测.当父母的自然 ...

  2. flask jQuery ajax 上传文件

    1.html 代码 <div> <form id="uploadForm" enctype="multipart/form-data" > ...

  3. 阿里Java研发工程师实习面经

    十分幸运 拿到阿里云的offer,感谢周围无数人对我的支持和鼓励,所以写篇面经希望可以帮助大家. 面试中,运气占很大一部分的,所以你们若是没有通过,一定不要气馁,继续加油. 每个努力的人 都值得钦佩, ...

  4. You And Me 不见不散!

    泰戈尔说: 有一个夜晚,我烧毁了所有的记忆, 从此我的梦就透明了: 有个早晨我扔掉了所有的昨天, 从此我的脚步就轻盈了! 越过山丘,才发现无人等候! 有段话最近很流行:20多岁的你,迷茫又着急,你想要 ...

  5. Centos Git1.7.1升级到Git2.2.1

    安装需求: ># yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel asciidoc ># ...

  6. ERP中的地区管理

    地区管理 地区管理主要实现地区数据的添加.编辑.查看.启用.禁用等功能,另外还包含地区选择控件封装. 业务功能点: 地区数据查看:地区列表树状展现,列表增加省.市.区.县.乡图标. 地区选择控件:选择 ...

  7. 有趣的冷知识:编程中Foo, Bar 到底什么意思?

    转自:编程中Foo, Bar 到底什么意思? 1 前言 在很多国外计算机书本和一些第三份开源软件的Demo中经常用到两个英文单词Foo,Bar.这到底是什么意思呢?从步入屌丝界的IT生活见到这两个单词 ...

  8. Radio Station

    B. Radio Station time limit per test:  2 seconds memory limit per test:  256 megabytes input: standa ...

  9. 深度解读GoogleNet之Inception V1

    GoogleNet设计的目的 GoogleNet设计的初衷是为了提高在网络里面的计算资源的利用率. Motivation 网络越大,意味着网络的参数较多,尤其当数据集很小的时候,网络更容易发生过拟合. ...

  10. Docker: Failed to get D-Bus connection: No connection to service

    Issue: When you execute systemctl command in docker container, you may receive following error. Erro ...