【阅读】《Linux高性能服务器编程》——第十五章:进程池和线程池

进程池和线程池

  • 15.1 进程池和线程池概述
  • 15.2 处理多客户
  • 15.3 半同步/半异步线程池实现
  • 15.4 使用进程池实现的简单的CGI服务器
  • 15.5 半同步/半反应堆线程池实现
  • 15.6 使用线程池实现的简单Web服务器
    • 15.6.1 http_conn类
    • 15.6.2 main函数

  动态创建子进程或子线程的缺点:

  • 动态创建进程或线程比较耗费时间,将导致较慢的客户响应;
  • 动态从创建的子进程或子线程通常只能为一个客户服务,将导致系统上产生大量的细微进程或线程,且进程、线程间切换将消耗大量的CPU事件;
  • 动态创建的子进程是当前进程的完整映像,当前进程必须谨慎地管理其分配的文件描述符和堆内存等系统资源,否则子进程可能会复制这些资源。

15.1 进程池和线程池概述

  进程池是由服务器预先创建一组子进程,从而实现并发。线程池中的线程数量应该和CPU数量差不多。进程池中所有子进程都运行着相同的代码,并具有相同的属性。由于进程池在服务启动之初就创建好了,所以每个子进程都相对“干净”。

  当有新任务到来时,主进程将通过某种方式选择进程池中的某一个子进程来为其服务,其主要通过两种方式:

  • 主进程使用某种算法来主动选择子进程;
  • 主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。

  主进程需要某种通知机制来告诉紫禁城由新任务需要处理,并传递必要的数据。最简单的方法是在父进程和子进程之间预先建立好一条管道,然后通过该管道来实现所有的进程间通信;也可以将这些数据定义为全局的。
【阅读】《Linux高性能服务器编程》——第十五章:进程池和线程池_第1张图片


15.2 处理多客户

  当客户的任务是无状态的,可以考虑使用不同的子进程来为该客户的不同请求服务。
【阅读】《Linux高性能服务器编程》——第十五章:进程池和线程池_第2张图片


15.3 半同步/半异步线程池实现

#ifndef PROCESSPOOL_H
#define PROCESSPOOL_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// 描述一个子进程的类,m_pid是目标子进程的PID,m_pipefd是父进程和子进程通信的管道
class process{
public:
    process():m_pid(1){}
public:
    pid_t m_pid;
    int m_pipefd[2];
};

// 进程池类,将其定义为模板类,模板参数是处理逻辑任务的类
template 
class processpool{
private:
    // 将构造函数定义为私有,只能通过creat静态函数创建实例
    processpool(int listenfd, int process_number = 8);

public:
    // 单体模式,保证程序最多创建一个processpool实例
    static processpool* create(int listenfd, int process_number = 8){
        if(!m_instance){
            m_instance = new processpool(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;
    // 子进程在池中的序号,从0开始
    int m_idx;
    // 每个进程都有一个epoll内核事件表,用m_epollfd标识
    int m_epollfd;
    // 监听sockte
    int m_listenfd;
    // 子进程通过m_stop决定是否停止运行
    int m_stop;
    // 保存所有子进程的描述信息
    process* m_sub_process;
    // 进程池静态实例
    static processpool* m_instance;
};
template 
processpool* processpool::m_instance = NULL;

// 用于处理信号的管道,以实现统一事件源,后面称之为信号管道
static int sig_pipefd[2];

static int setnonblocking(int fd){
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}
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);
    setnonblocking(fd);
}

// 从epollfd标志的epoll内核事件表中删除fd上的所有注册事件
static void removefd(int epollfd, int fd){
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}

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

static void addsig(int sig, void(handler)(int), bool restart = true){
    struct sigaciton 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);
}

// 进程池构造函数,参数listenfd是监听socket,process_number指定进程池中子进程的数量
template 
processpool::processpool(int listenfd, int process_number)
    :m_listenfd(listenfd),m_process_number(process_number),
    m_idx(-1),m_stop(false){
    assert((process_number>0)&&(process_number<=MAX_PROCESS_NUMBER));
    m_sub_process = new process[process_number];

    //创建process_number个子进程,并建立它们和夫进程之间的管道
    for(int i=0;i=0);
        if(m_sub_process[i].m_pid>0){
            close(m_sub_process[i].m_pipefd[1]);
            continue;
        }
        else{
            close(m_sub_process[i].m_pipefd[0]);
            m_idx = i;
            break;
        }
    }
}

// 统一事件源
template
void processpool::setup_sig_pipe(){
    // 创建epoll事件监听表和信号管道
    m_epollfd = epoll_create(5);
    assert(m_epollfd != -1);

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

    setnonblocking(sig_pipefd[1]);
    addfd(m_epollfd, sig_pipefd[0]);

    // 设置信号处理函数
    addsig(SIGHLD, sig_handler);
    addsig(SIGTERM, sig_handler);
    addsig(SIGINT, sig_handler);
    addsig(SIGPIPE, SIG_IGN);
}

// 父进程中m_idx为-1,子进程m_idx值大于等于0,由此判断运行父进程代码还是子进程代码
template
void processpool::run(){
    if(m_idx != -1){
        run_child();
        return;
    }
    run_parent();
}

template
void processpool::run_child(){
    setup_sig_pipe();

    // 每个子进程通过其在进程池的信号值m_idx找到与父进程通信的管道
    int pipefd = m_sub_process[m_idx].m_pipefd[1];
    // 子进程监听管道信号描述符pipefd,父进程通过其来通知子进程accept新连接
    addfd(m_epollfd, pipefd);

    epoll_event events[MAX_EVENT_NUMBER];
    T* users = new T[USER_PER_PROCESS];
    assert(users);
    int number = 0;
    int ret = -1;
    
    while(!m_stop){
        number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);
        if((number<0)&&(errno!=EINTR)){
            printf("epoll failure\n");
            break;
        }
        for(int i=0;i0){
                                    continue;
                                }
                                break;
                            }
                            case SIGTERM:
                            case SIGINT:{
                                m_stop = true;
                                break;
                            }
                            default:{
                                break;
                            }
                        }
                    }
                }
            }
            // 如果是其他可读数据, 必然是客户请求到来,调用逻辑处理对象的process方法处理
            else if(events[i].events&EPOLLIN){
                users[sockfd].process();
            }
            else{
                continue;
            }
        }
    }
    delete [] users;
    users = NULL;
    close(pipefd);
    // colse(m_listenfd);  // 应该由m_listenfd创建者关闭
    close(m_epollfd);
}

template
void processpool::run_parent(){
    setup_sig_pipe();

    // 父进程监听m_listenfd
    addfd(m_epollfd, m_listenfd);

    epoll_event events[MAX_EVENT_NUMBER];
    int sub_process_counter = 0;
    int new_conn = 1;
    int number = 0;
    int ret = -1;

    while(!m_stop){
        number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);
        if((number<0)&&(errno!=EINTR)){
            printf("epoll failure\n");
            break;
        }
        for(int i=0;i0){
                                    for(int i=0;i

15.4 使用进程池实现的简单的CGI服务器

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "./processpool.h"

// 用于处理客户CGI请求的类,可以作为processpool类的模板参数
class cgi_conn{
public:
    cgi_conn(){}
    ~cgi_conn(){}
    // 初始化客户端,清空缓冲区
    void init(int epollfd, int sockfd, const sockaddr_in& client_addr){
        m_epollfd = epollfd;
        m_sockfd = sockfd;
        m_address = client_addr;
        memset(m_buf, '\0', BUFFER_SIZE);
        m_read_idx = 0;
    }

    void process(){
        int idx = 0;
        int ret = -1;
        
        // 循环读取和分析客户数据
        while(true){
            idx = m_read_idx;
            ret = recv(m_sockfd, m_buf+idx, BUFFER_SIZE-1-idx, 0);
            // 若读操作发生错误,则关闭客户连接,如果是暂时无数据可读,则退出循环
            if(ret<0){
                if(errno!=EAGAIN){
                    removefd(m_epollfd, m_sockfd);
                }
                break;
            }
            // 如果对方关闭连接,则服务器也关闭连接
            else if(ret==0){
                removefd(m_epollfd, m_sockfd);
                break;
            }
            else{
                m_read_idx += ret;
                printf("user content is: %s\n", m_buf);
                //如果遇到"\r\n",则开始处理客户请求
                for(;idx=1)&&(m_buf[idx-1]=='\r')&&(m_buf[idx]=='\n')){
                        break;
                    }
                }
                // 如果没遇到字符"\r\n",则需要处理更多客户数据
                if(idx==m_read_idx){
                    continue;
                }
                m_buf[idx-1] = '\0';
                char* file_name = m_buf;
                // 判断客户要运行的CGI程序是否存在
                if(access(file_name, F_OK)==-1){
                    removefd(m_epollfd,m_sockfd);
                    break;
                }
                // 创建子进程执行CGI程序
                ret = fork();
                if(ret == -1){
                    removefd(m_epollfd, m_sockfd);
                    break;
                }
                else if(ret>0){
                    // 父进程只需要关闭连接
                    removefd(m_epollfd, m_sockfd);
                    break;
                }
                else{
                    // 子进程将标准输出定向到m_sockfd,并执行CGI程序
                    close(STDOUT_FILENO);
                    dup(m_sockfd);
                    execl(m_buf, m_buf, 0);
                    exit(0);
                }
            }
        }
    }
private:
    // 读缓冲区的大小
    static const int BUFFER_SIZE = 1024;
    static int m_epollfd;
    int m_sockfd;
    sockaddr_in m_address;
    char m_buf[BUFFER_SIZE];
    // 标记缓冲区中已经读入客户数据最后一个自己的下一个位置
    int m_read_idx;
};
int cgi_conn::m_epollfd = -1;

// 主函数
int main(int argc, char* argv[]){
    if(argc<=2){
        printf("usage: %s ip_address port_number \n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    int listenfd = socket(PF_INET, SOCK_STERAM, 0);
    assert(listenfd>=0);
    
    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
    assert(ret!=-1);

    ret = listen(listenfd, 5);
    assert(ret!=-1);

    processpool* pool = processpool::create(listenfd);
    if(pool){
        pool->run();
        delete pool;
    }
    close(listenfd);
    return 0;
}

15.5 半同步/半反应堆线程池实现

#ifndef THREADPOOL_H
#define THREADPOOL_H

#include 
#include 
#include 
#include 

#include "./locker.h"

// 线程池类
template
class threadpool{
public:
    threadpool(int thread_number=8,int max_requests=10000);
    ~threadpool();
    // 往请求队列中添加任务
    bool append(T* request);

private:
    // 工作线程运行函数,不断从工作队列中取出任务并执行
    static void* worker(void* arg);
    void run();

private:
    int m_thread_number;    // 线程数
    int m_max_requests;     // 请求队列中允许的最大请求数
    pthread_t* m_threads;   // 线程池的组数
    std::list m_workqueue;  // 请求队列
    locker m_queuelocker;   // 请求队列的互斥锁
    sem m_queuestat;        // 是否有任务需要处理
    bool m_stop;            // 是否结束线程
};

template
threadpool::threadpool(int thread_number, int max_requests):
    m_thread_number(thread_number),m_max_requests(max_requests),
    m_stop(false),m_threads(NULL){

    if((thread_number<=0)||(max_requests<=0)){
        throw std::exception();
    }
    m_thread = new pthread_t(m_thread_number);
    if(!m_threads){
        throw std::exception();
    }
    // 创建thread_number个线程,并设置为脱离线程
    for(int i=0;i
threadpool::~threadpool(){
    delete [] m_threads;
    m_stop = true;
}

template
bool threadpool::append(T* requests){
    // 操作工作队列要加锁
    m_queuelocker.lock();
    if(m_queuelocker.size()>m_max_requests){
        m_queuelocker.unlock();
        return false;
    }
    m_workqueue.push_back(requests);
    m_queuelocker.unlock();
    m_queuestat.post();     // 添加信号量
    return true;
}

template
void* threadpool::worker(void* arg){
    threadpool* pool = (threadpool*)arg;
    pool->run();
    return pool;
}

template
void threadpool::run(){
    while(!m_stop){
        m_queuestat.wait();
        m_queuelocker.lock();
        if(m_workqueue.empty()){
            m_queuelocker.unlock();
            continue;
        }
        T* request = m_workqueue.front();
        m_workqueue.pop_front();
        m_queuelocker.unlock();
        if(!request){
            continue;
        }
        request->process();
    }
}

#endif // !THREADPOOL_H

15.6 使用线程池实现的简单Web服务器

15.6.1 http_conn类

http_conn.h文件

#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "./locker.h"

class http_conn{
public:
    // 文件名的最大长度
    static const int FILENAME_LEN = 200;
    // 读缓冲区的大小
    static const int READ_BUFFER_SIZE = 2048;
    // 写缓冲区的大小
    static const int WRITE_BUFFER_SIZE = 1024;
    // HTTP请求方法,仅支持GET
    enum METHOD {GET = 0, POST, HEAD, PUT, DELETE,
                TRACE, OPTIONS, CONNECT, PATCH};
    // 解析客户请求时,主状态机所处的状态
    enum CHECK_STATE {
        CHECK_STATE_REQUESTLINE = 0,    // 当前正在分析请求行
        CHECK_STATE_HEADER,             // 当前正在分析头部字段
        CHECK_STATE_CONTENT
    };
    // 从在状态机的三种可能状态,即行的读取状态
    enum LINE_STATUS {
        LINE_OK = 0,            // 读取到一个完整的行
        LINE_BAD,               // 行出错
        LINE_OPEN               // 行数据尚且不完整
    };
    // 服务器处理HTTP请求的结果
    enum HTTP_CODE {
        NO_REQUEST,             // 请求不完整,需要继续获取客户数据
        GET_REQUEST,            // 获得了一个完整的客户请求
        BAD_REQUEST,            // 客户请求有语法错误
        NO_RECOURCE,
        FORBIDDEN_REQUEST,      // 客户对资源没有足够的访问权限
        FILE_REQUEST,
        INTERNAL_ERROR,         // 服务器内部错误
        CLOSE_CONNECTION        // 客户端已经关闭连接
    };

public:
    http_conn(){}
    ~http_conn(){}

public:
    // 初始化新接受的连接
    void init(int sockfd, const sockaddr_in& addr);
    // 关闭连接
    void close_conn(bool real_close = true);
    // 处理客户请求
    void process();
    // 非阻塞读操作
    bool read();
    // 非阻塞写操作
    bool write();

private:
    // 初始化连接
    void init();
    // 解析HTTP请求
    HTTP_CODE process_read();
    // 填充HTTP应答
    bool process_write(HTTP_CODE ret);

    // 被process_read调用以分析HTTP请求
    HTTP_CODE parse_request_line(char* text);
    HTTP_CODE parse_headers(char* text);
    HTTP_CODE parse_content(char* text);
    HTTP_CODE do_request();
    char* get_lin() { return m_read_buf+m_start_line; }
    LINE_STATUS parse_line();

    // 被process_write调用以填充HTTP应答
    void unmap();
    bool add_response(const char* format, ... );
    bool add_content(const char* content);
    bool add_status_line(int status, const char* title);
    bool add_headers(int content_length);
    bool add_content_length(int content_length);
    bool add_linger();
    bool add_blank_line();

public:
    // 所有socket上的事件都被注册到同一个epoll内核事件中
    static int m_epollfd;
    // 统计用户数量
    static int m_user_count;
    
private:
    // 该HTTP连接的socket和对方的socket地址
    int m_sockfd;
    sockaddr_in m_address;
    // 读缓冲区
    char m_read_buf[READ_BUFFER_SIZE];
    // 标识都缓冲住已经读入客户端数据的最后一个字节的下一个位置
    int m_read_idx;
    // 当前正在分析的字符在都缓冲区的位置
    int m_checked_idx;
    // 当前正在解析的行的起始位置
    int m_start_line;
    // 写缓冲区
    char m_write_buf[WRITE_BUFFER_SIZE];
    // 写缓冲区中待发送的字节数
    int m_write_idx;

    // 主状态机当前所处的状态
    CHECK_STATE m_check_state;
    // 请求方法
    METHOD m_method;

    // 客户请求的目标文件的完整路径
    char m_real_file[FILENAME_LEN];
    // 客户请求的目标文件的文件名
    char* m_url;
    // HTTP协议版本号,仅支持HTTP/1.1
    char* m_version;
    // 主机名
    char* m_host;
    // HTTP请求的消息体长度
    int m_content_length;
    // HTTP请求是否保持连接
    bool m_linger;

    // 客户请求的目标文件被mmap到内存中的起始位置
    char* m_file_address;
    // 目标文件的状态
    struct stat m_file_stat;
    // 采用writev执行写操作
    struct iovec m_iv[2];
    // 被写内存块的数量
    int m_iv_count;
};

#endif // !HTTPCONNECTION_H

http_conn.cpp文件

#include "./http_conn.h"

// 定义HTTP响应的一些状态信息
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problen serving the requested file.\n";
// 网站的根目录
const char* doc_root = "/var/www/html";

static int setnonblocking(int fd){
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}
// 将fd上的EPOLLIN和EPOLLET事件注册到epollfd指示上的epoll内核事件中
// 参数oneshot指定是否注册fd上的EPOLLONESHOT事件
static void addfd(int epollfd, int fd, bool one_shot){
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    if(one_shot){
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}
// 从epollfd标志的epoll内核事件表中删除fd上的所有注册事件
static void removefd(int epollfd, int fd){
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}
void modfd(int epollfd, int fd, int ev){
    epoll_event event;
    event.data.fd = fd;
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;

void http_conn::close_conn(bool real_close){
    if(real_close && (m_sockfd != -1)){
        removefd(m_epollfd, m_sockfd);
        m_sockfd = -1;
        m_user_count--;         // 关闭连接时,客户总量-1
    }
}

void http_conn::init(int sockfd, const sockaddr_in& addr){
    m_sockfd = sockfd;
    m_address = addr;
    // 下面两行避免TIME_WAIT状态,仅用于调试,实际使用应当去掉
    int reuse = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    addfd(m_epollfd, sockfd, true);
    m_user_count++;

    init();
}

void http_conn::init(){
    m_check_state = CHECK_STATE_REQUESTLINE;
    m_linger = false;
    m_method = GET;
    m_url = 0;
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_checked_idx = 0;
    m_read_idx = 0;
    m_write_idx = 0;
    memset(m_read_buf, '\0', READ_BUFFER_SIZE);
    memset(m_write_buf, '\0', WRITE_BUFFER_SIZE);
    memset(m_real_file, '\0', FILENAME_LEN);
}

// 从状态机,用于解析出一行内容
http_conn::LINE_STATUS http_conn::parse_line(){
    char temp;
    // checked_index指向buffer当前分析的字节
    // read_index指向buffer中客户数据为不得下一个字节
    // 第checked_index~(read_index-1)字节右下面进行分析
    for(; m_checked_idx1) && m_read_buf[m_checked_idx-1] == '\r'){
                m_read_buf[m_checked_idx-1] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
        return LINE_OPEN;
    }
}

// 循环读取客户数据,知道无数据可读或者对方关闭连接
bool http_conn::read(){
    if(m_read_idx >= READ_BUFFER_SIZE){
        return false;
    }
    
    int bytes_read = 0;
    while(true){
        bytes_read = recv(m_sockfd, m_read_buf+m_read_idx, READ_BUFFER_SIZE-m_read_idx, 0);
        if(bytes_read == -1){
            if(errno==EAGAIN || errno == EWOULDBLOCK){
                break;
            }
            return false;
        }
        else if(bytes_read==0){
            return false;
        }
        m_read_idx += bytes_read;
    }
    return ture;
}

// 解析HTTP请i去行,获取请求方法、目标URL、以及HTTP版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
    m_url = strpbrk(text, "\t");
    // 如果请求行中没有空白字符或者"\t",则请求有问题
    if(!m_url){
        return BAD_REQUEST;
    }
    *m_url++ = '\0';

    char* method = text;
    if(strcasecmp(method, "GET") == 0){     // 仅支持GET方法
        // printf("The request method is GET\n");
        m_method = GET;
    }
    else{
        return BAD_REQUEST;
    }

    m_url += strspn(m_url, "\t");
    m_version = strpbrk(m_url, "\t");
    if(!m_version){
        return BAD_REQUEST;
    }
    *m_version++ = '\0';
    m_version += strspn(m_version, "\t");
    // 仅支持HTTP/1.1
    if(strcasecmp(m_version, "HTTP/1.1")!=0){
        return BAD_REQUEST;
    }
    // 检查URL是否合法
    if(strncasecmp(m_url, "http://", 7) == 0){
        m_url += 7;
        m_url = strchr(m_url, '/');
    }
    if(!m_url || m_url[0]!='/'){
        return BAD_REQUEST;
    }
    printf("The request URL is: %s\n", url);
    // HTTP请求处理完毕,状态转移到头部字段分析
    m_check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

// 解析HTTP一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char* text){
    // 如果遇到空行,则标识头部字段全部解析完毕
    if(text[0] == '\0'){
        // 如果HTTP有消息体,则还需要读取消息体,状态机转移到CHECK_STATE_CONTENT状态
        if(m_content_length != 0){
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }

        // 否则得到一个完整的HTTP请求
        return GET_REQUEST;
    }

    // 处理Connection头部字段
    else if(strncasecmp(text, "Connection:", 11)==0){
        text += 11;
        text += strspn(text, "\t");
        if(strcasecmp(text, "keep-alive")==0){
            m_linger = true;
        }
    }
    // 处理Content-Length头部字段
    else if(strncasecmp(text, "Content-Length:", 15)==0){
        text += 15;
        text += strspn(text, "\t");
        m_content_length = atol(text);
    }
    // 处理Host头部字段
    else if(strcasecmp(text, "Host:", 5)==0){
        text += 5;
        text += strspn(text, "\t");
        m_host = text;
    }
    else{
        printf("opp! Unkonw header %s\n", text);
    }
    return NO_REQUEST;
}

// 我们没有真正的解析HTTP请求的消息体,只是判断是否被完全读入
http_conn::HTTP_CODE http_conn::parse_content(char* text){
    if(m_read_idx >= m_content_length+m_checked_idx){
        text[m_content_length] = '\0';
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

// 主状态机
http_conn::HTTP_CODE http_conn::process_read(){
    LINE_STATUS line_status = LINE_OK;   // 记录当前行的读取状态
    HTTP_CODE ret = NO_REQUEST;          // 记录HTTP请求的处理结果
    char* text = 0;
    // 主状态机,用于从buffer中取出所有完整的行
    while(((m_check_state==CHECK_STATE_CONTENT)&&(line_status==LINE_OK))||
          ((line_status=parse_line())==LINE_OK)){
        text = get_line();
        m_start_line = m_checked_idx;
        printf("got 1 http line:%s\n", text);
        // checkstate记录主状态机当前的状态
        switch(m_check_state){
            case CHECK_STATE_REQUESTLINE:{  // 状态一:分析请求行
                ret = parse_request_line(text);
                if(ret==BAD_REQUEST){
                    return BAD_REQUEST;
                }
                break;
            }
            case CHECK_STATE_HEADER:{       // 状态二:分析头部字段
                ret = parse_headers(text);
                if(ret == BAD_REQUEST){
                    return BAD_REQUEST;
                }
                else if(ret == GET_REQUEST){
                    return GET_REQUEST;
                }
                break;
            }
            case CHECK_STATE_CONTENT:{
                ret = parse_content(text);
                if(ret == GET_REQUEST){
                    return do_request();
                }
                line_status == LINE_OPEN;
                break;
            }
            default:{
                return INTERNAL_ERROR;
            }
        }
    }
    return NO_REQUEST;
}

// 得到一个完整的、正确的HTTP请求时分析目标文件属性
http_conn::HTTP_CODE http_conn::do_request(){
    strcpy(m_real_file, doc_root);
    int len = strlen(doc_root);
    strncpy(m_real_file+len, m_url, FILENAME_LEN-len-1);
    if(stat(m_real_file,&m_file_stat)<0){
        return NO_REQUEST;
    }
    if(!(m_file_stat.st_mode & S_IROTH)){
        return FORBIDDEN_REQUEST;
    }
    if(S_ISDIR(m_file_stat.st_mode)){
        return BAD_REQUEST;
    }

    int fd = open(m_real_file, O_RDONLY);
    m_file_address = (char*)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    return FILE_REQUEST;
}

// 对内存映射区执行munmap操作
void http_conn::unmap(){
    if(m_file_address){
        munmap(m_file_address, m_file_stat.st_size);
        m_file_address=0;
    }
}

// 写HTTP响应
bool http_conn::write(){
    int temp = 0;
    int bytes_have_send = 0;
    int bytes_to_send = m_write_idx;
    if(bytes_to_send==0){
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        init();
        return true;
    }
    while(1){
        temp = writev(m_sockfd, m_iv, m_iv_count);
        if(temp<=-1){
            // 如果TCP写缓冲没有空间,则等待下一轮EPOLLOUT事件
            if(errno==EAGAIN){
                modfd(m_epollfd, m_sockfd, EPOLLOUT);
                return true;
            }
            unmap();
            return false;
        }
        bytes_to_send -= temp;
        bytes_have_send += temp;
        if(bytes_to_send<=bytes_have_send){
            // 发送HTTP相应成功,根据Connection字段决定是否关闭连接
            unmap();
            if(m_linger){
                init();
                modfd(m_epollfd, m_sockfd, EPOLLIN);
                return true;
            }
            else{
                modfd(m_epollfd, m_sockfd, EPOLLIN);
                return false;
            }
        }
    }
}

// 往写缓冲中写入待发送的数据
bool http_conn::add_response(const char* format, ...){
    if(m_write_idx>=WRITE_BUFFER_SIZE){
        return false;
    }
    va_list arg_list;
    va_start(arg_list, format);
    int len = vsnprintf(m_write_buf+m_write_idx, WRITE_BUFFER_SIZE-1-m_write_idx, format, arg_list);
    if(len>=(WRITE_BUFFER_SIZE-1-m_write_idx)){
        return false;
    }
    m_write_idx += len;
    va_end(arg_list);
    return true;
}

bool http_conn::add_status_line(int status, const char* title){
    return add_response("%s %d %s\r\n", "HTTP/1.1",status,title);
}

bool http_conn::add_headers(int content_len){
    add_content_length(content_len);
    add_linger();
    add_blank_line();
}

bool http_conn::add_content_length(int content_len){
    return add_response("Content-Length: %d\r\n", content_len);
}

bool http_conn::add_linger(){
    return add_response("Connection: %s\r\n", (m_linger==true)?"keep-alive":"close");
}

bool http_conn::add_blank_line(){
    return add_response("%s", "\r\n");
}

bool http_conn::add_content(const char* content){
    return add_response("%s", content);
}

bool http_conn::process_write(HTTP_CODE ret){
    switch(ret){
        case INTERNAL_ERROR:{
            add_status_line(500, error_500_title);
            add_headers(strlen(error_500_form));
            if(!add_content(error_500_form)){
                return false;
            }
            break;
        }
        case BAD_REQUEST:{
            add_status_line(400, error_400_title);
            add_headers(strlen(error_400_form));
            if(!add_content(error_400_form)){
                return false;
            }
            break;
        }
        case NO_RESOURCE:{
            add_status_line(404, error_404_title);
            add_headers(strlen(error_404_form));
            if(!add_content(error_404_form)){
                return false;
            }
            break;
        }
        case FORBIDDEN_REQUEST:{
            add_status_line(403, error_403_title);
            add_headers(strlen(error_403_form));
            if(!add_content(error_403_form)){
                return false;
            }
            break;
        }
        case FILE_REQUEST:{
            add_status_line(200, ok_200_title);
            if(m_file_stat.st_size!=0){
                add_headers(m_file_stat.st_size);
                m_iv[0].iov_base = m_write_buf;
                m_iv[0].iov_len = m_write_idx;
                m_iv[1].iov_base = m_file_address;
                m_iv[1].iov_len = m_file_stat.st_size;
                m_iv_count = 2;
                return true;
            }
            else{
                const char* ok_string = "";
                add_headers(strlen(ok_string));
                if(!add_content(ok_string)){
                    return false;
                }
            }
        }
        default:{
            return false;
        }
    }
    m_iv[0].iov_base = m_write_buf;
    m_iv[0].iov_len = m_write_idx;
    m_iv_count = 1;
    return true;
}

// 线程池中的工作线程调用,处理HTTP请求的入口函数
void http_conn::process(){
    HTTP_CODE read_ret = process_read();
    if(read_ret==NO_REQUEST){
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        return;
    }
    bool write_ret = process_write(read_ret);
    if(!write_ret){
        close_conn();
    }
    modfd(m_epollfd, m_sockfd, EPOLLIN);
}

15.6.2 main函数

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "locker.h"
#include "threadpool.h"
#include "http_conn.h"

#define MAX_FD 65536
#define MAX_EVENT_NUMBER 10000

extern int addfd(int epollfd, int fd, bool one_shot);
extern int removerfd(int epollfd, int fd);

// 设置信号的处理函数
void addsig(int sig){
    struct sigaction sa;
    memset(&sa, '\0', sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags != SA_RESTART;
    sigfillset(&sa.sa_mask);
    assert(sigaction(sig, &sa, NULL)!=-1);
}

void show_error(int connfd, const char* info){
    printf("%s", info);
    send(connfd, info, strlen(info), 0);
    close(connfd);
}

int main(int argc, char* argv[]){
    if(argc<=2){
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    // 忽略SIGPIPE信号
    addsig(SIGPIPE, SIG_IGN);

    // 创建线程池
    threadpool* pool = NULL;
    try{
        pool = new threadpool;
    }
    catch(...){
        return 1;
    }

    // 预先为每个可能的客户连接分配一个http_conn对象
    http_conn* users = new http_conn[MAX_FD];
    assert(users);
    int user_count = 0;

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);
    struct linger tmp = {1, 0};
    setsockopt(listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
    assert(ret>=0);
    ret = listen(listenfd, 5);
    assert(ret>=0);

    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd!=-1);
    addfd(epollfd, listenfd, false);
    http_conn::m_epollfd = epollfd;

    while (true)
    {
        int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if((number<0)&&(errno!=EINTR)){
            printf("epoll failure\n");
            break;
        }
        for(int i=0;i= MAX_FD){
                    show_error(connfd, "Internal server busy");
                    continue;
                }
                // 初始化客户连接
                users[connfd].init(connfd, client_addres);
            }
            else if(events[i].events&(EPOLLRDHUP|EPOLLHUP|EPOLLERR)){
                // 如果有异常,直接关闭客户连接
                users[sockfd].close_conn();
            }
            else if(events[i].events&EPOLLIN){
                // 根据读的结果,决定时将任务添加到线程池还是关闭连接
                if(users[sockfd].read()){
                    pool->append(users+sockfd);
                }
                else{
                    users[sockfd].colse_conn();
                }
            }
            else if(events[i].events&EPOLLOUT){
                // 根据写的结果,决定是否关闭连接
                users[sockfd].close_conn();
            }
            else{}
        }
    }
    close(epollfd);
    close(listenfd);
    delete [] users;
    delete pool;
    return 0;
}

你可能感兴趣的:(服务器,linux,多进程,线程池,多线程)