此文與《linux高性能服務器編程》—— 一種高效的異步線程池的實現是姊妹篇。
(1)進程池框架
(2)源代碼及註釋
#ifndef PROCESSPOOL
#define PROCESSPOOL
#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 <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <set>
#include <map>
class process //工作進程類
{
public:
pid_t pid; //進程ID
int pipefd[2]; //主進程與各工作進程的通信管道
public:
process(): pid(-1) { };
};
template <typename T> // T是客戶類
class processpool //進程池類模板,使用單例模式
{
private:
const int MAX_PROCESS_NUMBER = 16; //工作進程的最大數量
const int USER_PER_PROCESS = 1000; //每個進程負責的客戶的最大數量
const int MAX_EVENT_NUMBER = 1000; //epoll內核事件表所能容納的事件的最大數量
int process_number; //工作進程的實際數量
int idx; //區分主/工作進程的標誌,對於主進程,idx = -1,對於工作進程,idx = 其在進程池中的序號
int listenfd; //指向監聽socket的fd,主/工作進程共用同一個
std::map<int,int> pidtoidx; //建立子進程ID與子進程在進程信息數組中的序號的映射
process* sub_process; //管理各工作進程的信息
static processpool* instance; //指向此類的唯一對象
private:
processpool(int listenfd, int process_number = 8);
void run_parent();
void run_child();
public:
static processpool* create(int listenfd, int process_number = 8) //返回此進程池類的唯一對象
{
if(!instance)
{
instance = new processpool(listenfd, process_number);
}
return instance;
}
~processpool()
{
delete [] sub_process;
}
void run(); //開始主/工作進程的運行
};
template <typename T>
processpool<T>* processpool<T>::instance = NULL;
int sig_pipefd[2]; //信號管道,主/工作進程都需要
int setnonblocking(int fd) //將fd設爲非阻塞
{
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
void addfd(int epollfd, int fd, std::set<int> &used_fd)
//將fd的讀事件插入epoll內核事件表內,並打開此fd的ET模式,並用used_fd記錄此fd
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN |EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
used_fd.insert(fd);
setnonblocking(fd);
}
void removefd(int epollfd, int fd, std::set<int> &used_fd)
//將fd的所有事件從epoll內核事件表內移除,並從used_fd中移去fd
{
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
used_fd.erase(fd);
}
void close_fd(int epollfd, std::set<int> &used_fd)
//從epoll內核事件表內除去所有的fd及相關事件
{
for(auto fd : used_fd)
{
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
used_fd.clear();
close(epollfd);
}
void sig_handler(int sig)
//信號捕捉函數,即接收到此信號時,把信號從[1]端送入信號管道內,實現統一事件源
{
int save_errno = errno;
int msg = sig;
send(sig_pipefd[1], (char*)&msg, 1, 0);
errno = save_errno;
}
void addsig(int sig, void(*_handler)(int), bool restart = true)
//對信號設置信號捕捉函數
{
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = _handler;
if(restart)
{
sa.sa_flags |= SA_RESTART;
}
sigfillset(&sa.sa_mask);
assert(sigaction(sig, &sa, NULL) != -1);
}
template <typename T>
processpool<T>::processpool(int _listenfd, int _process_number)
:listenfd(_listenfd), process_number(_process_number),idx(-1)
{
assert((process_number > 0) && (process_number <= MAX_PROCESS_NUMBER));
sub_process = new process[process_number]; //管理各工作進程的信息的數組
assert(sub_process);
for(int i = 0;i < process_number;++i)
{
//使主進程與各工作進程的通信管道變爲全雙工
int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sub_process[i].pipefd);
assert(ret == 0);
sub_process[i].pid = fork(); //fork出工作進程
assert(sub_process[i].pid >= 0);
if(sub_process[i].pid == 0) //對於工作進程
{
close(sub_process[i].pipefd[1]); //關閉通信管道[1]端,只使用[0]端
idx = i; //在進程池中的序號
break;
}
else
{
pidtoidx[sub_process[i].pid] = i; //建立子進程ID與在進程信息數組中的序號的映射
close(sub_process[i].pipefd[0]); //關閉通信管道[0]端,只使用[1]端
continue; //不分配序號,m_idx = -1
}
}
}
void setup_sig_pipe(int epollfd, std::set<int> &used_fd)
//將信號管道的讀事件插入epoll內核事件表內,實現統一事件源
{
//使信號管道變爲全雙工
int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sig_pipefd);
assert(ret != -1);
setnonblocking(sig_pipefd[1]);
//將信號管道[0]端的讀事件插入epoll內核事件表內
addfd(epollfd, sig_pipefd[0], used_fd);
//設置以下信號的信號捕捉函數
addsig(SIGCHLD, sig_handler);
addsig(SIGTERM, sig_handler);
addsig(SIGINT, sig_handler);
//忽略此信號
addsig(SIGPIPE, SIG_IGN);
}
template <typename T>
void processpool<T>::run()
//依據m_idx的值,在不同的進程內運行不同的代碼
{
if(idx != -1)
run_child();
else
run_parent();
}
template <typename T>
void processpool<T>::run_child()
//工作進程運行的代碼
{
//用於保存被插入到epoll內核事件表內的fd
std::set<int> used_fd;
int epollfd = epoll_create(5);
//將信號管道的讀事件插入epoll內核事件表內,實現統一事件源
setup_sig_pipe(epollfd, used_fd);
int pipefd = sub_process[idx].pipefd[0];
//將和主進程通信的管道[0]端的讀事件插入epoll內核事件表內
addfd(epollfd, pipefd, used_fd);
epoll_event events[MAX_EVENT_NUMBER];
//保存客戶數據的數組
T* users = new T[USER_PER_PROCESS];
assert(users);
int number = 0;
int ret = -1;
bool stop = false;
while(!stop)
{
number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if((number < 0) && (errno != EINTR))
{
printf("epoll failure\n");
break;
}
for(int i = 0;i < number && !stop;++i)
{
int sockfd = events[i].data.fd;
if((sockfd == pipefd) && (events[i].events & EPOLLIN))
//來自主進程的數據,說明有客戶發起連接請求
{
int client = 0;
ret = recv(sockfd, (char*)&client, sizeof(client), 0);
if(ret <= 0)
{
continue;
}
//接受客戶連接請求
else
{
struct sockaddr_in address;
socklen_t addrlength = sizeof(address);
int connfd = accept(listenfd, (struct sockaddr*)&address,
&addrlength);
if(connfd < 0)
{
printf("error is %d\n", errno);
continue;
}
else if(connfd >= USER_PER_PROCESS)
//此進程管理的用戶數量已達到上限,關閉此連接
{
const char *msg = "sorry! There are too many clients!\n";
send(connfd, msg, sizeof(msg), 0);
close(connfd);
continue;
}
//將此連接socket的讀事件插入epoll內核事件表內
addfd(epollfd, connfd, used_fd);
//初始化客戶數據,以連接socket的fd值作爲客戶序號
users[connfd].init(epollfd, connfd, address);
}
}
else if((sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN))
//來自信號管道的數據
{
char signals[1024];
ret = recv(sig_pipefd[0], signals, sizeof(signals), 0);
if(ret <= 0)
{
continue;
}
else
{
for(int i = 0;i < ret && !stop;++i)
{
switch(signals[i])
{
case SIGCHLD:
//若工作進程沒有子進程,則可直接跳過
{
continue;
}
case SIGTERM:
case SIGINT:
//結束進程
{
stop = true;
break;
}
}
}
}
}
else if(events[i].events & EPOLLIN)
//來自某連接socket的數據,說明客戶發起了某個請求
{
//調用客戶類的方法進行處理
users[sockfd].process(used_fd);
}
}
}
delete [] users; //回收動態資源
close_fd(epollfd, used_fd); //關閉所有fd
close(sig_pipefd[1]);
}
template <typename T>
void processpool<T>::run_parent()
//主進程運行的代碼
{
//用於保存被插入到epoll內核事件表內的fd
std::set<int> used_fd;
int epollfd = epoll_create(5);
//將信號管道的讀事件插入epoll內核事件表內,實現統一事件源
setup_sig_pipe(epollfd, used_fd);
//將監聽socket的讀事件插入epoll內核事件表內
addfd(epollfd, listenfd, used_fd);
epoll_event events[MAX_EVENT_NUMBER];
//挑選工作進程的起點序號
int sub_process_counter = 0;
int number = 0;
int ret = -1;
bool stop = false;
while(!stop)
{
number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if((number < 0) && (errno != EINTR))
{
printf("epoll failure\n");
break;
}
for(int i = 0;i < number && !stop;++i)
{
int sockfd = events[i].data.fd;
if(sockfd == listenfd)
//來自監聽socket的數據,說明有客戶發起連接請求
{
int j = sub_process_counter;
do
//採用Round Robin方式挑選出一個工作進程
{
if(sub_process[j].pid != -1)
break;
j = (j + 1) % process_number;
} while(j != sub_process_counter);
if(sub_process[j].pid == -1)
//所有工作進程已結束運行
{
stop = true;
break;
}
sub_process_counter = (j + 1) % process_number;
//向挑選出的工作進程通知:有客戶發起連接請求
int msg = 1;
send(sub_process[j].pipefd[1], (char*)&msg, sizeof(msg), 0);
}
else if((sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN))
//來自信號管道的數據
{
char signals[1024];
ret = recv(sockfd, signals, sizeof(signals), 0);
if(ret <= 0)
{
continue;
}
else
{
for(int j = 0;j < ret && !stop;++i)
{
switch(signals[j])
{
case SIGCHLD:
//有工作進程結束運行
{
pid_t pid;
int stat;
while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
//每次處理一個已結束運行的工作進程
{
int idx = pidtoidx[pid]; // 獲取子進程序號
sub_process[idx].pid = -1; //標誌子進程已結束運行
//將與此進程的通信管道[1]端從epoll內核事件表內刪除,並關閉
removefd(epollfd, sub_process[idx].pipefd[1], used_fd);
close(sub_process[idx].pipefd[1]);
}
stop = true;
//遍歷一遍所有工作進程的信息,若所有工作進程都已結束,則主進程也結束
for(int i = 0;i < process_number && stop;++i)
{
if(sub_process[i].pid != -1)
{
stop = false;
}
}
break;
}
case SIGTERM:
case SIGINT:
//收到中止信號,結束前先殺死所有工作進程
{
for(int k = 0;k < process_number;++k)
{
int pid = sub_process[k].pid;
if(pid != -1)
kill(pid, SIGTERM);
}
stop = true;
break;
}
}
}
}
}
}
}
//關閉所有fd
close_fd(epollfd, used_fd);
close(sig_pipefd[1]);
}
#endif
(3)用於測試進程池的簡單客戶類的實現
class client //客戶類
{
private:
static const int BUFFER_SIZE = 1024; //緩衝區大小
int epollfd; //負責此客戶的工作進程的epollfd
sockaddr_in client_addr; //客戶socket地址
char buf[BUFFER_SIZE]; //緩衝區
public:
int connfd; //與此客戶通信的socket
void init(int _epollfd, int _connfd, const sockaddr_in& _client_addr)
//初始化客戶數據
{
epollfd = _epollfd;
connfd = _connfd;
client_addr = _client_addr;
memset(buf, '\0', BUFFER_SIZE);
}
void process(std::set<int> &used_fd)
//當客戶有數據發來時,進程的迴應:把客戶發來的數據原封不動地返回給客戶,僅用於測試
{
memset(buf, '\0', sizeof(buf));
int ret = recv(connfd, buf, sizeof(buf), 0);
if((ret < 0 && errno != EAGAIN) || ret == 0)
//客戶關閉連接或發生讀錯誤,則關閉連接
{
//回收文件描述符
removefd(epollfd, connfd, used_fd);
close(connfd);
}
else if(ret > 0)
{
//將數據返還給客戶
send(connfd, buf, strlen(buf), 0);
}
}
};