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. hdu 3433 A Task Process 二分+dp

    A Task Process Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) T ...

  2. [bzoj3668][Noi2014]起床困难综合症/[洛谷3613]睡觉困难综合症

    来自FallDream的博客,未经允许,请勿转载,谢谢. 21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳.作为一名青春阳光好少年,atm 一直坚持与起床困难综 ...

  3. python正则表达式与Re库

    正则表达式是用来简洁表达一组字符串的表达式,一行胜千言,有点类似于数列的通项公式. 在python中提供了re库(regular expression)即正则表达式库,内置于python的标准库中,导 ...

  4. Delphi7.0常用函数-属性-事件

    abort 函数 引起放弃的意外处理 addexitproc 函数 将一过程添加到运行时库的结束过程表中 addr 函数 返回指定对象的地址 adjustlinebreaks 函数 将给定字符串的行分 ...

  5. ZhuSuan 是建立在Tensorflow上的贝叶斯深层学习的 python 库

    ZhuSuan 是建立在Tensorflow上的贝叶斯深层学习的 python 库. 与现有的主要针对监督任务设计的深度学习库不同,ZhuSuan 的特点是深入到贝叶斯推理中,从而支持各种生成模式:传 ...

  6. promise应用及原生实现promise模型

    一.先看一个应用场景 发送一个请求获得用户id, 然后根据所获得的用户id去执行另外处理.当然这里我们完全可以使用回调,即在请求成功之后执行callback; 但是如果又添加需求呢?比如获得用户id之 ...

  7. leetcode刷题笔记342 4的幂

    题目描述: 给定一个整数 (32位有符整数型),请写出一个函数来检验它是否是4的幂. 示例:当 num = 16 时 ,返回 true . 当 num = 5时,返回 false. 问题进阶:你能不使 ...

  8. Android Studio精彩案例(五)《JSMS短信验证码功能实现》

    转载本专栏文章,请注明出处,尊重原创 .文章博客地址:道龙的博客 很多应用刚打开的时候,让我们输入手机号,通过短信验证码来登录该应用.那么,这个场景是怎么实现的呢?其实是很多开放平台提供了短信验证功能 ...

  9. Android简易实战教程--第四十七话《使用OKhttp回调方式获取网络信息》

    在之前的小案例中写过一篇使用HttpUrlConnection获取网络数据的例子.在OKhttp盛行的时代,当然要学会怎么使用它,本篇就对其基本使用做一个介绍,然后再使用它的接口回调的方式获取相同的数 ...

  10. ObjectOutputStream 和 ObjectInputStream的使用

    一.看一下API文档 ObjectOutputStream : ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream.可以使用 ObjectInp ...