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. [usaco6.1.1Postal Vans]

    来自FallDream的博客,未经允许,请勿转载,谢谢. 给你一个4*n的棋盘,问从(1,1)出发恰好经过所有格子一次的走法数量.(n<=1000) 插头dp,用f[i][j][k]表示转移到第 ...

  2. CTSC2017 铁牌记

    Day 0: 先到了丽都酒店. 看见北京八十中学生在发胸牌手册和T恤,领完之后开始分房间. 我和ryc一屋,lyd和ysq一屋,yzy和gzz分到了珀利酒店,老师尝试了半天把我们分到一个酒店,失败了. ...

  3. Mysql锁机制--读锁

    Mysql 系列文章主页 =============== 1 准备数据 1.1 建表 1.1.1 建立 Employee表 DROP TABLE IF EXISTS employee; CREATE ...

  4. Jenkins构建时间Poll Scm的设置

    每15分钟构建一次:H/15 * * * *   或*/15 * * * * 每天8点构建一次:0 8 * * * 每天8点~17点,两小时构建一次:0 8-17/2 * * * 周一到周五,8点~1 ...

  5. SVN错误:SVN Working copy XXX is too old

    出错原因: 这是因为使用了低版本的svn生成了.svn内文件内容,但是,使用高版本svn同步时便出现该问题. 解决方法: 找到报错对应的文件夹,里面有个名为.svn的文件夹,删除这个文件夹(这是svn ...

  6. .net通用CMS快速开发框架——问题1:Dapper通用的多表联合分页查询怎么破?

    最近在弄一个东东,类似那种CMS的后台管理系统,方便作为其它项目的初始化框架用的. 现在遇到个问题,如标题所示:Dapper通用的多表联合分页查询怎么破? 难道只能通过拼接sql或者使用存储过程吗?我 ...

  7. Codeforces Round #305 (Div. 2) A. Mike and Fax 暴力回文串

     A. Mike and Fax Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/548/pro ...

  8. 【kmp】 剪花布条 HDU - 2087

    [题意概述] [解题思路] 从左往右找,看有几个匹配的字符串,但要注意例如 aaaaaa  aa的输出是3 而不是5,可以这样理解:画布需要减去,一旦减去也就没有了的 [AC] #include< ...

  9. ACM Tempter of the Bone

    小狗在古老的迷宫(maze)中发现了一个骨头,这使它非常着迷.然而,当他把它捡起来的时候,迷宫开始摇晃,狗狗可以感觉到地面下沉(sinking).他意识到这块骨头是一个陷阱(trap),他拼命地想摆脱 ...

  10. Dynamics 365 你所期待的子网格编辑终于来了

    Dynamics 365的online版本已经在11月1号发布了,on-premises版也在没几天后发布,今天略看了一眼 what's new 一眼就看到了 editable grids,这个不用我 ...