《linux高性能服務器編程》—— 一種高效的異步進程池的實現

此文與《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);
        }
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章