相关网络编程函数:http://blog.csdn.net/somehow1002/article/details/72648743
预先派生子进程(preforking)是让服务器在启动阶段调用fork创建一个子进程池。每个客户请求由当前可用子进程池中的某个闲置子进程处理。
预先派生子进程服务器程序
1.初始版本(accept无上锁保护)
static int nchildren;该版本子进程accept,会有“惊群”现象发生。即当一个客户连接到达时,所有可用子进程均会被唤醒。具体哪个子进程接受请求由操作系统系统来决定。
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:server [<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);
signal(SIGINT,sig_int);
for(;;)
pause();
}
void sig_int(int signo){
int i;
for(i=0;i<nchildren;i++)
kill(pids[i],SIGTERM);
while(wait(NULL)>0)
;
if(errnoo!=ECHILD)
perror("wait error");
exit(0);
}
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;
child_main(i,listenfd,addrlen);
}
void child_main(int i,int listenfd,int addrlen){
int connfd;
void str_echo(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);
str_echo(connfd);
close(connfd);
}
}
事实上,在该版本的程序中,应用程序在调用accept函数时,会有多个子进程阻塞在accept函数调用中,对此,可以对accept使用上锁保护。
2.accept使用文件上锁保护
文件锁相关函数
struct flock{据此,修改原先代码如下:
short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}
static struct flock lock_it,unlock_it;
static int lock_fd=-1;
void my_lock_init(char *pathname){
char lock_file[1024];
//must 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);
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;
}
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");
}
}
main函数中,在派生子进程的循环前加入my_lock_init函数的调用。
my_lock_init("/tmp/lock.XXXXXX"); //one lock file for all childrenchild_main函数需要在调用accept之前获取文件锁,在accept返回之后释放文件锁。
for(i=0;i<nchildren;i++)
pids[i]=child_make(i,listenfd,addrlen);
for(;;){但是,毫无疑问,文件锁较耗时,因此我们使用线程锁改写程序。
clilen=addrlen;
my_lock_wait();
connfd=accept(listenfd,cliaddr,&clilen);
my_lock_release();
web_child(connfd);
close(connfd);
}
3.accept使用线程上锁保护
使用线程锁要求:
1.互斥锁变量必须存放在由所有进程共享的内存区中
2.必须告知线程函数库这是在不同进程之间共享的互斥锁。
3.线程库支持PTHREAD_PROCESS_SHARED属性
新版本的锁函数
static pthread_mutex_t *mptr; //actual mutex will be in shared memory
void my_lock_init(char *pathname){
int fd;
pthread_mutex_t mattr;
fd=open("/dev/zero",O_RDWR,0);
mptr=Mmap(0,sizeof(pthread_mutex_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setshared(&mattr,PTHREAD_PROCESS_SHARED);
pthread_mutex_init(mptr,&mattr);
}
void my_lock_wait(){
pthread_mutex_lock(mptr);
}
void my_lock_release(){
pthread_mutex_unlock(mptr);
}
4.传递描述符
这个版本与上述版本不同,该版本只让父进程调用accept,然后把所接受的已链接套接字通过unix域套接字传递给某个子进程。这样做绕过了为所有子进程的accept调用提供上锁保护的可能需求,但是需要父进程来处理哪个子进程来处理一个到来的客户连接。
typedef struct{
pid_t child_pid;
int child_pipefd;
int child_status;
long child_count;
}Child;
Child *cptr;
static int nchildren;
int main(int argc,char **argv){
int listenfd,i,navail,maxfd,nsel,connfd,rc;
void sig_int(int);
pid_t child_make(int ,int ,int );
ssize_t n;
fd_set rset,masterset;
socklen_t addrlen,clilen;
struct sockaddr *cliaddr;
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:server [<host>] <port#> <#children>");
FD_ZERO(&masterset);
FD_SET(listenfd,&masterset);
maxfd=listenfd;
cliaddr=malloc(addrlen);
nchildren=atoi(argv[argc-1]);
navail=nchildren;
cptr=calloc(nchildren,sizeof(Child));
for(i=0;i<nchildren;i++){
child_make(i,listenfd,addrlen);
FD_SET(cptr[i].child_pipefd,&masterset);
maxfd=max(maxfd,cptr[i].child_pipefd);
}
signal(SIGINT,sig_int);
for(;;){
rset=masterset;
if(navail<=0)
FD_CLR(listenfd,&rset); //turn off if no available children
nsel=select(maxfd+1,&rset,NULL,NULL,NULL);
if(FD_ISSET(listenfd,&rset)){
clilen=addrlen;
connfd=accept(listenfd,cliaddr,&clilen);
for(i=0;i<nchildren;i++)
if(cptr[i].child_status==0)
break;
if(i==nchildren)
err_quit("no available children");
cptr[i].child_status=1;
cptr[i].child_count++;
navail--;
n=write_fd(cptr[i].child_pipefd," ",1,connfd);
close(connfd);
if(--nsel==0)
continue;
}
for(i=0;i<nchildren;i++){
if(FD_ISSET(cptr[i].child_pipefd,&rset)){
if((n=read(cptr[i].child_pipefd,&rc,1))==0)
err_quit("child %d terminated unexpected",i);
cptr[i].child_status=0;
navail++;
if(--nsel==0)
break;
}
}
}
}
pid_t child_make(int i,int listenfd,int addrlen){
int sockfd[2];
pid_t pid;
void child_main(int ,int ,int );
Socketpair(AF_LOCAL,SOCK_STREAM,0,sockfd);
//parent
if((pid=fork())>0){
close(sockfd[1]);
cptr[i].child_pid=pid;
cptr[i].child_pipefd=sockfd[0];
cptr[i].child_status=0;
return pid;
}
Dup2(sockfd[1],STDERR_FILENO);
close(sockfd[0]);
close(sockfd[1]);
close(listenfd);
child_main(i,listenfd,addrlen);
}
void child_main(int i,int listenfd,int addrlen){
char c;
int connfd;
ssize_t n;
void str_echo(int);
printf("child %ld starting\n",(long)getpid());
for(;;){
if((n=Read_fd(STDERR_FILENO,&c,1,&connfd))==0)
err_quit("read_fd returned 0");
if(connfd<0)
err_quit("no descriptor from read_fd");
str_echo(connfd);
close(connfd);
Write(STDERR_FILENO," ",1); //tell parent we're ready again
}
}