前面讲过允许多个进程在引用同一个监听套接口的描述字上调用accept,但是这种做法仅仅适用于在内核中实现accept的原子Berkeley的内核。相反,作为一个库函数实现accept的System V内核可能不允许这样做,这是因为SVR4的流实现机制和库函数版本的accept并非一个原子操作引起的。
所以,我们就必须在accept前后加上某种形式的锁,这样任意时刻只有一个子进程阻塞在accept调用上,其他子进程则阻塞在试图获取用于保护accept的锁上。
我们可用使用fcntl函数来实现POSIX文件上锁功能。
serv03.c
----------------------------
#include "global.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: serv03 [ ] <#children>"); nchildren = atoi(argv[argc-1]); pids = Calloc(nchildren, sizeof(pid_t));
/* init the file lock */ my_lock_init("/tmp/lock.XXXXXX"); /* one lock file for all children */
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);
/* terminate 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); }
|
main函数唯一的改动就是在派生子进程的循环之前增加一个文件锁的初始化函数my_lock_init
child03.c
----------------------------------
#include "global.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; my_lock_wait(); connfd = Accept(listenfd, cliaddr, &clilen); my_lock_release();
web_child(connfd); /* process the request */ Close(connfd); } }
|
child_main函数唯一的改动就是在调用accept之前获取文件锁,在accept返回后释放文件锁。
lock_fcntl.c
---------------------------------
/* include my_lock_init */ #include "global.h"
static struct flock lock_it, unlock_it; static int lock_fd = -1; /* fcntl() will fail if my_lock_init() not called */
void my_lock_init(char *pathname) { char lock_file[1024];
/* 4must copy caller's string, in case it's a constant */ strncpy(lock_file, pathname, sizeof(lock_file)); lock_fd = Mkstemp(lock_file);
Unlink(lock_file); /* but lock_fd remains open */
lock_it.l_type = F_WRLCK; lock_it.l_whence = SEEK_SET; lock_it.l_start = 0; lock_it.l_len = 0;
unlock_it.l_type = F_UNLCK; unlock_it.l_whence = SEEK_SET; unlock_it.l_start = 0; unlock_it.l_len = 0; } /* end my_lock_init */
/* include my_lock_wait */ void my_lock_wait() { int rc; while ( (rc = fcntl(lock_fd, F_SETLKW, &lock_it)) < 0) { if (errno == EINTR) continue; else err_sys("fcntl error for my_lock_wait"); } }
void my_lock_release() { if (fcntl(lock_fd, F_SETLKW, &unlock_it) < 0) err_sys("fcntl error for my_lock_release"); } /* end my_lock_wait */
|
现在这个新版本的预先派生子进程服务器程序在SVR4系统上照样可以工作,因为它保证每次只有一个子进程阻塞在accept调用中。
但是这种围绕accept的上锁增加了服务器的进程控制CPU时间。
Apache1.1版本在预先派生子进程之后,如果实现允许所以子进程都阻塞在accept调用中,那就使用上篇介绍的不加锁的预先派生子进程技术,否则就使用本节介绍的包绕accept的文件上锁技术。
这种技术的运行结果如下:
$ ./serv03 173.26.100.162 12345 10
child 14613 starting
child 14614 starting
child 14615 starting
child 14616 starting
child 14617 starting
child 14618 starting
child 14619 starting
child 14620 starting
child 14621 starting
child 14622 starting
user time = 0.008, sys time = 0.16401
程序源代码包见<<客户/服务器程序设计范式---迭代服务器程序>>后的附件
阅读(1340) | 评论(0) | 转发(0) |