进程池实现----高效的半同步/半异步并发模式的进程池

为何要用进程池?

当我们在写并发服务器时,若每当一个客户端连接请求到来时,我们就为其创建一个进程来服务。这样动态的创建进程,无疑是比较耗时的,这将导致较慢的客户端相应。

进程池的概念

在服务器启动前我们就创建一组进程,我们将这组进程在逻辑抽象成一个池子。

当我们需要一个工作进程来处理新到来的客户端请求时,我们可以直接从进程池中取得一个执行实体(进程),而无需动态的调用fork函数来创建进程。相比于动态创建子进程,选择一个已经存在的子进程代价显然要小得多。至于主进程如何选择子进程,有两种方式:

  1. 主进程使用某种算法来主动选择子进程,最简单、最常用的算法是随机算法和轮流选取算法,但更优秀、更智能的算法将使任务在各个工作进程中更均匀地分配,从而减轻服务器的整体压。
  2. 主进程和所有子进程通过一个共享的工作队列来同步。(即生产者消费者模型)

进程池的实现

进程池的概念很简单,重要的是我们怎么通过代码实现它,下面给出高效的半同步/半异步并发模式的进程池实现。
高效的半同步/半异步并发模式
在该模式中,主进程只负责监听连接请求的到来,当有连接请求到来时,选取一个子进程 通过与子进程的管道来通知子进程有连接请求到来,子进程负责接受连接和对该连接服务。可以看到,每个进程都维持自己的事件循环,它们各自独立监听不同的事件。因此,在这种高效的半同步/半异步模式中,每个进程都工作在异步模式下,所以它并非严格意义上的半同步/半异步模式。

进程池的代码:

#ifndef _PROCESS_POOL_H
#define _PROCESS_POOL_H
#include<arpa/inet.h>
#include<assert.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<errno.h>
#include<fcntl.h>
#include<stdio.h>
#include<sys/wait.h>
/*描述一个子进程的类,pid是目标子进程的pid,pipefd是父子进程之间通信的管道*/
class process{
	public:
		pid_t pid;		//子进程pid
		int pipefd[2];	//子进程与父进程之间的管道
	public:
		process():pid(-1){}
};

/*进程池类,定义为模板方便代码复用,模板参数是处理逻辑任务的类*/
template<typename T>
class process_pool{
	private:
		/*进程池允许的最大子进程数量*/
		static const int MAX_PROCESS_NUMBER = 16;
		/*一个子进程最多能处理多少个客户端*/
		static const int MAX_CLIENT_NUMBER = 65535;
		/*epoll的监听上限*/
		static const int MAX_EVENT_NUMBER = 10000;
		/*进程池中子进程的个数*/
		int process_number;
		/*进程在进程池中的编号*/
		int process_id;
		/*监听的socket*/
		int listenfd;
		/*进程的epollfd*/
		int epollfd;
		/*是否停止运行的标志*/
		int stop;
		/*进程池中所有子进程的描述信息*/
		process* sub_process;
		/*进程池静态实例*/
		static process_pool<T>* instance;
	private:
		/*单例模式中的懒汉式,将构造函数私有化,通过下面的getinstance静态函数来获取process_pool实例,保证全局只有唯一一个实例,参数listenfd是监听socket,它必须在创建进程池之前被创建,否则子进程无法直接引用它,参数process_number指定进程池中子进程的数量*/
		process_pool(int listenfd,int process_number = 0):listenfd(listenfd),process_number(process_number),process_id(-1)
		{
			sub_process =  new process[process_number];
			assert(sub_process != nullptr);

			/*创建process_number个子进程,并建立它们和父进程之间的管道*/
			for(int i = 0;i < process_number;i++)
			{
				int ret = socketpair(AF_UNIX,SOCK_STREAM,0,sub_process[i].pipefd);
				assert(ret == 0);

				sub_process[i].pid = fork();
				assert(sub_process[i].pid >= 0);

				if(sub_process[i].pid > 0)
				{
					close(sub_process[i].pipefd[0]);
				}
				else{
					close(sub_process[i].pipefd[1]);
					process_id = i;
					break;
				}
			}
		}
		void set_sig_pipe(bool isparent);
		void run_child();
		void run_parent();
	public:
		static process_pool<T>* getinstance(int listenfd,int process_number = 8)
		{
			if(instance == nullptr)
			{
				instance = new process_pool<T>(listenfd,process_number);
			}
			return instance;
		}
		~process_pool()
		{
			delete []sub_process;
		}
		/*启动进程池*/
		void run();
};
template<typename T>
process_pool<T>* process_pool<T>::instance = nullptr;

/*处理信号的管道,以实现统一信号源*/
static int sig_pipefd[2];

static void set_nonblock(int fd)
{
	int flag = fcntl(fd,F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(fd,F_SETFL,flag);
}

static void addfd(int epollfd,int fd)
{
	epoll_event event;
	event.data.fd =  fd;
	event.events = EPOLLIN | EPOLLET;
	epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
	set_nonblock(fd);
}

static void sig_handler(int sig)
{
	send(sig_pipefd[1],(char*)&sig,1,0);
}

static void addsig(int sig,void(handler)(int))
{
	struct sigaction sa;
	sa.sa_handler = handler;
	sa.sa_flags = SA_RESTART;
	sigfillset(&sa.sa_mask);
	sigaction(sig,&sa,NULL);
}

/*统一事件源*/
template<typename T>
void process_pool<T>::set_sig_pipe(bool isparent)
{
	epollfd  = epoll_create(5);
	assert(epollfd != -1);

	int ret =  socketpair(AF_UNIX,SOCK_STREAM,0,sig_pipefd);
	assert(ret != -1);

	addfd(epollfd,sig_pipefd[0]);

	addsig(SIGINT,sig_handler);
	addsig(SIGPIPE,SIG_IGN);
	/*若为父进程则设置SIGCHLD信号,用来回收子进程*/
	if(isparent)
		addsig(SIGCHLD,sig_handler);
}

/*父进程的编号为-1,子进程的编号大于等于0,利用编号来判断启动父进程还是子进程代码*/
template<typename T>
void process_pool<T>::run()
{
	if(process_id == -1)
		run_parent();
	else run_child();
}

template<typename T>
void process_pool<T>::run_child()
{
	set_sig_pipe(false);

	/*子进程监听与父进程的管道*/
	addfd(epollfd,sub_process[process_id].pipefd[0]);

	epoll_event events[MAX_EVENT_NUMBER];
	T *users = new T[MAX_CLIENT_NUMBER];
	stop = false;
	while(!stop)
	{
		int n = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
		if(n < 0)
		{
			if(errno == EINTR)
				continue;
			perror("epoll_wait error:");
			break;
		}
		for(int i = 0;i < n;i++)
		{
			int socketfd = events[i].data.fd;
			if(socketfd == sub_process[process_id].pipefd[0])
			{
				int num = -1;
				while(1)
				{
					int ret = recv(socketfd,(char*)&num,sizeof(num),0);
					if(ret < 0)
					{
						if(errno != EAGAIN)
							perror("sub_process pipefd recv error:");
						break;
					}
					//父进程关闭与该子进程的管道一端,则跳出
					if(ret == 0)
						break;
					struct sockaddr_in conn_addr;
					socklen_t conn_addr_len = sizeof(conn_addr);
					int connfd = accept(listenfd,(struct sockaddr*)&conn_addr,&conn_addr_len);
					if(connfd < 0)
					{
						perror("accept error:");
						break;
					}
					char ip[100];
					printf("process_id is %d accept client address = %s\n",process_id,inet_ntop(AF_INET,&conn_addr.sin_addr,ip,sizeof(ip)));
					addfd(epollfd,connfd);
					/*模板类必须实现init方法,以初始化一个客户端连接*/
					users[connfd].init(connfd,conn_addr,epollfd);
				}
			}
			/*只有SIGINT信号才会使信号管道有事件发生,直接使进程结束即可*/
			else if(socketfd  == sig_pipefd[0])
			{
				char buf[BUFSIZ];
				while(true)
				{
					int ret = recv(socketfd,buf,sizeof(buf),0);
					if(ret < 0)
					{
						if(errno != EAGAIN)
							perror("sub_process sig_pipefd recv error:");
						break;
					}
				}
				stop = true;
			}
			/*其他可读事件必然是客户端请求到来,调用逻辑处理对象的process方法处理即可*/
			else if(events[i].events & EPOLLIN)
			{
				printf("users process=========\n");
				users[socketfd].process();
			}
		}
	}

	printf("child pid = %d exit\n",getpid());

	/*向父进程发送自己的编号,以便父进程及时释放与该进程通信的管道资源*/
	send(sub_process[process_id].pipefd[0],(char*)&process_id,1,0);

	delete[]users;
	close(epollfd);
	close(sig_pipefd[0]);
	close(sig_pipefd[1]);
	close(sub_process[process_id].pipefd[0]);
}
template<typename T>
void process_pool<T>::run_parent()
{
	set_sig_pipe(true);

	/*父进程监听listenfd*/
	addfd(epollfd,listenfd);
	/*监听与子进程通信的所有管道*/
	for(int i = 0;i < process_number;i++)
	{
		addfd(epollfd,sub_process[i].pipefd[1]);
	}

	epoll_event events[MAX_EVENT_NUMBER];
	int cnt = 0;
	int sub_process_number = process_number; 
	stop = false;
	while(!stop)
	{
		int n = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
		if(n < 0)
		{
			if(errno != EINTR)
			{
				perror("parent process epoll_wait error:");
				break;
			}
			continue;
		}
		for(int i = 0;i < n;i++)
		{
			int socketfd = events[i].data.fd;
			/*若有客户端连接请求到来,轮流选取子进程来此处理客户端请求,若无子进程,则服务终止*/
			if(socketfd == listenfd)
			{
				int num = 0;
				int j = cnt;
				do{
					j = (j + 1) % process_number;
				}while(sub_process[j].pid == -1 && j != cnt);

				if(sub_process[j].pid == -1)
				{
					stop = true;
					break;
				}
				cnt = j;
				send(sub_process[j].pipefd[1],(char*)&num,1,0);
				printf("send request to child %d\n",j);
			}
			/*处理父进程接收到的信号*/
			else if(socketfd == sig_pipefd[0])
			{
				char signals[BUFSIZ];
				while(1)
				{
					memset(signals,'\0',sizeof(signals));
					int ret = recv(socketfd,signals,sizeof(signals),0);
					if(ret == -1)
					{
						if(errno != EAGAIN)
							perror("parent process error:");
						break;
					}
					for(int i = 0;i < ret;i++)
					{
						switch(signals[i])
						{
							case SIGCHLD:
							{
								printf("parents get SIGCHLD\n");
								int stat;
								pid_t pid;
								while((pid = waitpid(-1,&stat,WNOHANG)) > 0)
								{
									sub_process_number--;
									printf("waitpid is %d\n",pid);
									/*若所有子进程退出了,则父进程也退出*/
									if(!sub_process_number)
									{
										stop = true;
									}
								}
								break;
							}
							case SIGINT:
							{
								printf("parents get SIGINT\n");

								sleep(1);
								printf("kill all the child now\n");
								//若父进程接收到终止信号,就将所有子进程杀死
								for(int i  = 0;i < process_number;i++)
								{
									pid_t pid = sub_process[i].pid; 
									if(pid != -1)
									{
										printf("send kill to process pid = %d\n",pid);
										int ret = kill(pid,SIGINT);
										if(ret == -1)
										{
											perror("kill error:");
										}
									}
								}
								break;
							}
						}
					}
				}
			}
			/*若是其他事件,则必然为子进程发送的编号*/
			else if(events[i].events & EPOLLIN)
			{
				char id;
				int ret = recv(socketfd,&id,sizeof(id),0);
				if(ret < 0)
				{
					perror("parent_process_pipefd recv error:");
				}
				printf("sub_process send id = %d\n",(int)id);

				/*将子进程的pid设置为-1,以标记子进程已经退出*/
				sub_process[(int)id].pid =  -1;

				/*将管道的文件描述符从epoll中移除*/
				ret = epoll_ctl(epollfd,EPOLL_CTL_DEL,socketfd,NULL);
				if(ret == -1)
					perror("epoll_ctl error");

				/*释放与子进程通信的管道一端*/
				close(sub_process[(int)id].pipefd[1]);
			}
		}
	}
	close(epollfd);
	close(sig_pipefd[0]);
	close(sig_pipefd[1]);
}
#endif

应用进程池的server代码,该代码的功能是通过客户端请求服务器运行一个程序,并将运行结果返回给客户端。

#include"process_pool.h"
#include<arpa/inet.h>
/*用于处理客户CGI请求的类,它可以作为process_pool类的模板参数*/
class cgi_conn
{
	private:
		int sockfd;
		struct sockaddr_in address;
		static int epollfd;
		char buf[BUFSIZ];
		/*标记缓冲区中已经读入的客户端数据的最后一个字节的下一个字节*/
		int read_indx;
		void removefd()
		{
			epoll_ctl(epollfd,EPOLL_CTL_DEL,sockfd,NULL);
			close(sockfd);
		}
	public:
		/*初始化客户端连接,清空缓冲区*/
		void init(int sockfd,struct sockaddr_in &address,int epollfd)
		{
			this->sockfd = sockfd;
			this->address = address;
			this->epollfd = epollfd;
			memset(buf,'\0',sizeof(buf));
			read_indx = 0;
		}
		void process()
		{
			int indx = 0;
			while(true)
			{
				indx = read_indx;
				int ret = recv(sockfd,buf + indx,sizeof(buf)-indx,0);
				if(ret < 0)
				{
					if(errno != EAGAIN)
					{
						perror("client recv error:");
						/*关闭连接*/
						removefd();
					}
					break;
				}
				/*若对端关闭,则关闭连接*/
				else if(ret == 0)
				{
					removefd();
					break;
				}

				read_indx += ret;
				/*判断是否有\n*/
				for(;indx < read_indx;indx++)
				{
					if(buf[indx] == '\n')
					{
						break;
					}
				}
				/*若没有\n,则还需读取数据*/
				if(indx == read_indx)
					continue;
				buf[indx] = '\0';

				char *file_name = buf;
				/*判断客户要运行的CGI程序是否存在*/
				if(access(file_name,F_OK) == -1)
				{
					perror("access error");
					removefd();
					break;
				}

				/*创建子进程来执行CGI程序*/
				pid_t pid = fork();
				if(pid == -1)
				{
					perror("fork error:");
					removefd();
					break;
				}
				else if(pid > 0)
				{
					/*父进程只需要关闭连接*/
					removefd();
					break;
				}
				else{
					/*子进程将标准输出重定向到sockfd上,并运行CGI程序*/
					dup2(sockfd,STDOUT_FILENO);
					execl(buf,buf,NULL);
					exit(0);
				}
			}
		}
};
int cgi_conn::epollfd = -1;

int main(int argc,const char* argv[])
{
	if(argc < 3)
	{
		printf("usage: %s ip_adress port_number\n",argv[0]);
		exit(1);
	}

	const char *ip = argv[1];
	int port = atoi(argv[2]);

	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET,ip,&address.sin_addr);

	int listenfd = socket(AF_INET,SOCK_STREAM,0);

	bind(listenfd,(struct sockaddr*)&address,sizeof(address));

	listen(listenfd,3);

	process_pool<cgi_conn>* pool = process_pool<cgi_conn>::getinstance(listenfd);
	if(pool)
	{
		pool->run();
		delete pool;
	}
	close(listenfd);
	return 0;
}





参考:Linux高性能服务器编程 游双著

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值