进程池服务:让服务器在启动的某个阶段创建一个进程池,每个客户请求由当前可用子进程池中的某个(闲置)子进程处理。使用进程池的优点在于可以节省fork的开销直接就能处理新到的客户。缺点就是父进程必须父进程必须在服务器启动阶段猜测需要预先派生多少子进程。如果某个时刻客户数恰好等于子子进程总数,那么新到的客户将会被忽略,直到有一个子进程可以被重新使用。如果启动的时候创建过多的子进程,那么就会出现进程池中很多子进程闲置,这样就浪费了系统资源。这里我们暂时不考虑该问题,先看看那线程池实现多客户服务的基本结构。
在设计线程池的时候需要注意。当多个进程在引用同一个套接字的描述符上调用select时会出现冲突。因为在socket结构中为存放本套接字就绪之时应该唤醒哪些进程而分配的仅仅是一个进程ID的空间。如果有多个进程在等待同一个套接字,那么内核就必须唤醒的是阻塞在select调用中的所有进程。因为它不知道哪些进程受刚变得就绪的这个套接字影响。我们在这里的处理是:在派生子进程之前加上锁,在子进程中调用accept之前获取锁,在accept返回之后释放文件锁。这里提供两种方式上锁:文件上锁,线程上锁。
服务端程序:
/*=============================================================================
# FileName: processpool.c
# Desc: The process pool handles multi-client services
# Author: licaibiao
# LastChange: 2017-02-15
=============================================================================*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>
#include <sys/file.h>
#define LISTENLEN 30
#define SERV_PORT 6666
static int nchildren;
static int lock_fd = -1;
static pid_t *pids;
static struct flock lock_it, unlock_it;
/* fcntl() will fail if my_lock_init() not called */
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); /* 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;
}
void my_lock_wait()
{
int rc;
while ( (rc = fcntl(lock_fd, F_SETLKW, &lock_it)) < 0) {
if (errno == EINTR)
continue;
else
printf("fcntl error for my_lock_wait");
}
}
void my_lock_release()
{
if (fcntl(lock_fd, F_SETLKW, &unlock_it) < 0)
printf("fcntl error for my_lock_release");
}
void web_child(int fd)
{
char *write = "web_child";
send(fd,write,strlen(write)+1,0);
}
void child_main(int i, int listenfd, int addrlen)
{
int connfd;
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);
}
}
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 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)
{
printf("wait error");
exit(0);
}
//pr_cpu_time();
exit(0);
}
int main(int argc, char **argv)
{
int listenfd, connfd, i;
const int on = 1;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENLEN);
nchildren = LISTENLEN;
pids = calloc(nchildren, sizeof(pid_t));
my_lock_init("/tmp/lock.XXXXXX"); /* one lock file for all children */
for (i = 0; i < nchildren; i++)
pids[i] = child_make(i, listenfd, sizeof(struct sockaddr_in)); /* parent returns */
signal(SIGINT, sig_int);
for ( ; ; )
pause(); /* everything done by children */
}
客户端测试程序:
/*=============================================================================
# FileName: client.c
# Desc: a client for test
# Author: licaibiao
# LastChange: 2017-02-15
=============================================================================*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>
#include <sys/file.h>
#define PORT 6666
#define MAXLINE 1024
#define FOKKNUM 3
#define LOOPNUM 4
int main(int argc, char **argv)
{
int i, j, fd, num, ret;
pid_t pid;
char readbuff[MAXLINE];
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
for (i = 0; i < FOKKNUM; i++)
{
if ( (pid = fork()) == 0) /* child */
{
for (j = 0; j < LOOPNUM; j++)
{
fd = socket(AF_INET,SOCK_STREAM,0);
ret = connect(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret < 0)
{
printf("connect err \n");
}
num = recv(fd,readbuff,MAXLINE,0);
if(num > 0)
{
printf("pid = %d recv data:%s\n",getpid(),readbuff);
}else
{
printf("recv err \n");
}
memset(readbuff, 0, MAXLINE);
close(fd); /* TIME_WAIT on client, not server */
//printf("loopnum %d\n",j);
}
//printf("child %d done\n", i);
exit(0);
}
/* parent loops around to fork() again */
}
while (wait(NULL) > 0) /* now parent waits for all children */
;
if (errno != ECHILD)
printf("wait error");
exit(0);
}
测试结果:
root@ubuntu:/home/share/test# ./processpool &
[1] 6760
root@ubuntu:/home/share/test#
child 6767 starting
child 6769 starting
child 6768 starting
child 6766 starting
child 6771 starting
child 6770 starting
child 6773 starting
child 6772 starting
child 6765 starting
child 6775 starting
child 6774 starting
child 6777 starting
child 6776 starting
child 6764 starting
child 6779 starting
child 6778 starting
child 6781 starting
child 6780 starting
child 6782 starting
child 6784 starting
child 6786 starting
child 6763 starting
child 6785 starting
child 6783 starting
child 6788 starting
child 6787 starting
child 6789 starting
child 6790 starting
child 6762 starting
child 6761 starting
root@ubuntu:/home/share/test# ./client 192.168.0.4
pid = 6794 recv data:web_child
pid = 6794 recv data:web_child
pid = 6793 recv data:web_child
pid = 6794 recv data:web_child
pid = 6793 recv data:web_child
pid = 6794 recv data:web_child
pid = 6793 recv data:web_child
pid = 6793 recv data:web_child
pid = 6792 recv data:web_child
pid = 6792 recv data:web_child
pid = 6792 recv data:web_child
pid = 6792 recv data:web_child
root@ubuntu:/home/share/test#
root@ubuntu:/home/share/test#
root@ubuntu:/home/share/test#
注意:该程序只是搭建了一个基本的结构,并没有做进程管理。
文件上锁方法可以移植到所有支持POSIX兼容的系统,不过它涉及文件系统操作,比较的耗时。线程上锁保护accept,这种方法不仅适用于同一进程内各线程之间的上锁,还适用于不同进程之间的上锁。上锁的代码如下:
/* include my_lock_init */
#include "unpthread.h"
#include <sys/mman.h>
static pthread_mutex_t *mptr; /* actual mutex will be in shared memory */
void my_lock_init(char *pathname)
{
int fd;
pthread_mutexattr_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_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(mptr, &mattr);
}
/* end my_lock_init */
/* include my_lock_wait */
void my_lock_wait()
{
pthread_mutex_lock(mptr);
}
void my_lock_release()
{
pthread_mutex_unlock(mptr);
}