进程池的基本思想
由主进程管理所有监听socket,而各个子进程分别管理属于自己的连接的socket,子进程可以自己调用accept来接受新连接,这样父进程就无需向子进程传递socket,而只需要简单的说一声."我检测到了有新的连接,你来接受一下“
进程池的代码逻辑
- 每个进程类都有一个m_pid用来标识自己,还有一个管道成员来统一信号源,处理信号事件
- 进程池类使用单例模式创建,其构造函数为private,该类任何时刻只能有一个实例对象。通过create函数创建对象
- 进程池类的构造函数中创建好process_number个子进程,将每个每个子进程的pid赋给子进程对象的成员m_pid,同时为每个进程创建一根与父进程通信的双向管道
- 双向管道的一端既能读也能写,但如果从pipe[0]写入,只能从pipe[1]读出
- 进程池类中的m_idx成员用来标识每个子进程在进程池中的序号,父进程的m_idx为-1;
- run函数通过m_idx判断接下来是执行子进程的代码还是父进程的代码
- 所有进程通过m_stop这个bool值来决定是否退出
- run_parent()的代码逻辑
- 通过setup_sig_pipe创建同一事件源的信号管道
- 父进程有自己的epoll事件循环,每当有新的连接到来时,就采用Round Robin算法将新连接分配给一个子进程处理,被分配地子进程必须是“活着地”,通过其m_pid成员是否不等于 -1判断,父进程通过m_pipefd管道成员来通知子进程接受新连接
- 信号通过sig_pipefd管道传给父进程,父进程使用signals数组存储收到的信号,并逐个处理信号,当收到SIGCHLD信号时,表示有子进程退出,使用waitpid无阻塞地回收子进程,遍历子进程数组,通过对比pid和每个子进程地m_pid成员,找到退出地子进程,将其m_pid设为-1,标识该进程已退出
- 遍历子进程数组,查看是否所有子进程地m_pid都为-1,如果是,则父进程也退出(m_stop = true);
- 当收到SIGTERM和SIGINT信号时,杀死所有子进程
- run_child()代码逻辑
- 通过setup_sig_pipe创建统一事件源的信号管道
- 每个子进程也有自己的epoll事件循环,当m_pipefd管道上有可读事件发生时,表示有新的连接到来,处理这个新的socket连接,并将它得读写事件注册到自己得epoll内核事件表
- 处理信号事件的逻辑和父进程类似但更简单多
- 处理客户请求,调用逻辑处理对象的process办法处理之
代码实现
#ifndef PROCESSPOOL_H_
#define PROCESSPOOL_H_
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/wait.h>
#include <sys/stat.h>
//子进程的类
class process
{
public:
process():m_pid(-1){}
public:
pid_t m_pid;
int m_pipefd[2];
};
//进程池类
template <typename T>
class processpool
{
private:
//将构造函数设为私有,只能通过create来创建实例
processpool(int listenfd,int process_number = 8);
public:
//单例模式
static processpool<T>* create(int listenfd,int process_number = 8)
{
if(!m_instance)
{
m_instance = new processpool<T>(listenfd,process_number);
}
return m_instance;
}
~processpool()
{
delete [] m_sub_process;
}
//启动进程池
void run();
private:
void setup_sig_pipe();
void run_parent();
void run_child();
private:
//进程池允许的最大子进程数量
static const int MAX_PROCESS_NUMBER = 16;
//每个子进程最多能处理的客户数量
static const int USER_PER_PROCESS = 65536;
//epoll最多能处理的事件数
static const int MAX_EVENT_NUMBER = 10000;
//进程总数
int m_process_number;
int m_epollfd;
int m_listenfd;
//子进程在池中的序号
int m_idx;
//子进程通过m_stop决定是否停止运行
int m_stop;
//保存所有子进程的描述信息
process* m_sub_process;
static processpool<T>* m_instance;
};
template <typename T>
processpool<T>* processpool<T>::m_instance = NULL;
//用于处理信号的管道,统一事件源
static int sig_pipefd[2];
static int setnonblocking(int fd)
{
int old_flag = fcntl(fd,F_GETFL);
int new_flag = old_flag | O_NONBLOCK;
fcntl(fd,F_SETFL,new_flag);
return old_flag;
}
static void addfd(int epollfd,int fd)
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN|EPOLLET;
epoll_ctl(