linux高并发服务器开发(有详细注释)

在这里插入图片描述

main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <signal.h>
#include "locker.h"
#include "threadpool.h"
#include "http_conn.h"
#include <assert.h>

#define MAX_FD  65535 // 文件描述符的最大个数
#define MAX_EVENT_NUMBER 10000 //一次监听的最大的数量

//添加信号捕捉
void addsig(int sig, void(handler)(int)){
    struct sigaction sa;
     //清空sa,都设置为0
    memset(&sa, '\0', sizeof(sa));
    //信号捕捉之后的处理函数
    sa.sa_handler= handler;
    //功能:将信号集中的所有的标志位置为1 ,设置信号集都是阻塞的
    sigfillset(&sa.sa_mask);
    // assert() 的用法很简单,我们只要传入一个表达式,它会计算这个表达式的结果:
    // 如果表达式的结果为“假”,assert() 会打印出断言失败的信息,并调用 abort() 
    // 函数终止程序的执行;如果表达式的结果为“真”,assert() 就什么也不做,程序继续往后执行。
    assert( sigaction( sig, &sa, NULL ) != -1 );
}

//添加文件描述符到epoll中
extern void addfd(int epollfd, int fd, bool one_shot);
//从epoll中删除文件描述符
extern void removefd(int epollfd, int fd);
//修改epoll中的文件描述符
extern void modfd(int epollfd, int fd, int ev);

//arg表示参数的个数,argv[]是参数
int main(int argc, char * argv[]){

    if(argc <= 1){
        printf("按照如下格式运行: %s port_number\n", basename(argv[0])); //basename是获取基础的名字
    }

    //获取端口号 argv[0]是程序名, argv[1]是端口号
    int port = atoi(argv[1]);

    //网络中一段断开连接,而另一端还在写数据,可能导致SIGPIPE信号
    //对SIGPIPE信号进行处理,SIG_IGN是一个函数,表示忽略它
    addsig(SIGPIPE, SIG_IGN);

    //创建线程池,http_conn是一个任务类
    threadpool<http_conn> * pool = NULL;
    try{
        pool = new threadpool<http_conn>;
    }   
    catch(...)
    {
        return 1;
    }

    //创建一个数组,用于保存所有的客户端信息
    http_conn * users = new http_conn[ MAX_FD ];
    
    //下面就是TCP连接到基本步骤
    int listenfd = socket(PF_INET, SOCK_STREAM, 0);

    //设置端口复用
    int reuse = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    //绑定
    struct sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
    address.sin_addr.s_addr = INADDR_ANY;
    bind(listenfd, (struct sockaddr *)&address, sizeof(address));


    //监听
    listen(listenfd, 5);

    //创建epoll对象,事件数组,添加
    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create(5);

    //将监听的文件描述符添加到epoll中
    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 < number; i++){

            int sockfd = events[i].data.fd;

            if(sockfd == listenfd){
                //有客户端链接进来
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);
                int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);

                if(connfd < 0){
                    printf( "errno is: %d\n", errno);
                    continue;
                }

                if(http_conn::m_user_count >= MAX_FD){
                    //目前连接数满了
                    //给客户端写一个信息:服务器内部正忙。
                    close(connfd);
                    continue;
                }
                //将新的客户的数据初始化,放到数组当中
                users[connfd].init(connfd, client_address);

            }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].close_conn();
                }

            }else if(events[i].events & EPOLLOUT){
                //如果写失败了
                if(!users[sockfd].write()){ //一次性写完所有的数据
                    users[sockfd].close_conn();
                }
            }
        }
    }

    close(epollfd);
    close(listenfd);
    delete []users;
    delete pool;
    return 0;
}

http_conn.h

#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H

#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include "locker.h"
#include <sys/uio.h>
#include <string.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};
    
    /*
        解析客户端请求时,主状态机的状态
        CHECK_STATE_REQUESTLINE:当前正在分析请求行
        CHECK_STATE_HEADER:当前正在分析头部字段
        CHECK_STATE_CONTENT:当前正在解析请求体
    */
    enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
    
    // 从状态机的三种可能状态,即行的读取状态,分别表示
    // 1.读取到一个完整的行 2.行出错 3.行数据尚且不完整
    enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };

    /*
        服务器处理HTTP请求的可能结果,报文解析的结果
        NO_REQUEST          :   请求不完整,需要继续读取客户数据
        GET_REQUEST         :   表示获得了一个完成的客户请求
        BAD_REQUEST         :   表示客户请求语法错误
        NO_RESOURCE         :   表示服务器没有资源
        FORBIDDEN_REQUEST   :   表示客户对资源没有足够的访问权限
        FILE_REQUEST        :   文件请求,获取文件成功
        INTERNAL_ERROR      :   表示服务器内部错误
        CLOSED_CONNECTION   :   表示客户端已经关闭连接了
    */
    enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
    
public:
    http_conn(){}
    ~http_conn(){}

public:
    //处理客户端请求
    void process(); //解析http请求,将响应信息返回由主线程进行写出
    void init(int sockfd, const sockaddr_in & addr);  //初始化新接收的连接
    void close_conn();  //关闭连接
    bool read();   //非阻塞的读
    bool write();  //非阻塞的写
    void unmap();  //释放内存映射


private:
    void init();   //初始化连接其余的信息

    //解析HTTP请求
    HTTP_CODE process_read(); //解析HTTP请求
    HTTP_CODE parse_request_line(char * text); //解析请求首行
    HTTP_CODE parse_headers(char * text); //解析请求头
    HTTP_CODE parse_content(char * text); //解析请求体

    //处理并写会HTTP请求
    bool process_write( HTTP_CODE ret );  // 填充HTTP应答
    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();
    bool add_content(const char* content);
    bool add_content_type();
    bool add_response(const char* format, ...);

    LINE_STATUS parse_line();   //解析一行
    char * get_line(){ return m_read_buf + m_start_line; }
    HTTP_CODE do_request();  //对行的具体的处理

public:
    static int m_epollfd;    //所有的socket上的事件都被注册到同一个epollfd;
    static int m_user_count; //统计用户的数量

private:
    int m_sockfd;           // 该HTTP连接的socket和对方的socket地址
    sockaddr_in m_address;  // 通信的socket地址,用于保存客户信息

    char m_read_buf[READ_BUFFER_SIZE];  // 读缓冲区
    int m_read_idx;                     // 标识读缓冲区中已经读入的客户端数据的最后一个字节的下标
    int m_check_index;                  // 当前正在分析的字符在读缓冲区的位置
    int m_start_line;                   // 当前正在解析的行的起始位置

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

    // 客户请求的目标文件的完整路径,其内容等于doc_root + m_url, doc_root是网站根目录
    char m_real_file[ FILENAME_LEN ];   
    char * m_url;                       // 请求目标文件的文件名
    char * m_version;                   // 协议版本,只支持HTTP1.1
    char * m_host;                      // 主机名
    int m_content_length;               // HTTP请求的的消息总长度
    bool m_linger;                      // HTTP请求是否要保持连接 

    // 目标文件的状态。通过它我们可以判断文件是否存在、是否为目录、是否可读,并获取文件大小等信息
    struct stat m_file_stat;            
    
    char m_write_buf[ WRITE_BUFFER_SIZE ];
    int m_write_idx;
    char* m_file_address;               // 客户请求的目标文件被mmap到内存中的起始位置
    struct iovec m_iv[2];               // 我们将采用writev来执行写操作,所以定义下面两个成员,
    int m_iv_count;                     // 表示被写内存块的数量。

    // struct iovec
    // {
    //     void *iov_base;	/* Pointer to data.  */  //起始位置
    //     size_t iov_len;	/* Length of data.  */   //长度
    // };

    int bytes_to_send;     //将要发送的数据的字节数
    int bytes_have_send;   //已经发送的字节数


 };



#endif

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 problem serving the requested file.\n";


int http_conn::m_epollfd = -1;    //所有的socket上的事件都被注册到同一个epollfd;
int http_conn::m_user_count = 0;  //统计用户的数量

//网站的根目录
const char* doc_root = "/home/nowcoder/webserver1/resources";

//设置文件描述符非阻塞
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;
}
//添加文件描述符到epoll中
void addfd(int epollfd, int fd, bool one_shot){
    epoll_event event;
    event.data.fd = fd;
    // 内核2.6.17以后,对端如果异常断开会出现EPOLLIN和EPOLLRDHUP异常,这里直接通过事件去判断
    //event.events = EPOLLIN |  EPOLLRDHUP;
    event.events = EPOLLIN |  EPOLLRDHUP;
    if(one_shot){
        // 防止同一个通信被不同的线程处理
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    //设置文件描述符非阻塞
    setnonblocking(fd);
}

//从epoll中删除文件描述符
void removefd(int epollfd, int fd){
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}

//修改epoll中的文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
extern void modfd(int epollfd, int fd, int ev){
    epoll_event event;
    event.data.fd = fd;
    event.events = ev | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

//初始化新接收的连接
void http_conn::init(int sockfd, const sockaddr_in & addr){

    m_sockfd = sockfd;
    m_address = addr;

    //设置端口复用
    int reuse = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    //添加到epoll对象中
    addfd(m_epollfd, m_sockfd, true);
    m_user_count++; //用户数+1

    init(); 
}

void http_conn::init(){

    bytes_to_send = 0;
    bytes_have_send = 0;

    m_check_state = CHECK_STATE_REQUESTLINE;    // 初始状态为检查请求行
    m_linger = false;       // 默认不保持链接  Connection : keep-alive保持连接

    m_method = GET;         // 默认请求方式为GET
    m_url = 0;              
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_check_index = 0;
    m_read_idx = 0;
    m_write_idx = 0;

    bzero(m_read_buf, READ_BUFFER_SIZE);
    bzero(m_write_buf, READ_BUFFER_SIZE);
    bzero(m_real_file, FILENAME_LEN);

}

// 关闭连接
void http_conn::close_conn(){
    if(m_sockfd != -1){
        removefd(m_epollfd, m_sockfd);
        m_sockfd = -1;
        m_user_count--; //关闭一个连接,客户总数量-1
    }
}
//循环读取客户端数据,直到无数据刻度或者对方关闭连接
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){
            //对方关闭链接
            printf("client is closed!\n");
            return false;
        }
        m_read_idx += bytes_read;
    }
    printf("读取到了数据:\n");
    printf("%s\n", m_read_buf);
    return true;
}
// 写HTTP响应
bool http_conn::write(){
    printf("write+++++++++++++++++++++++++++++++++++++++\n");
    // printf("一次性写完数据\n");

    int temp = 0;

    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, EPOLLIN);
                return true;
            }
            unmap();
            return false;
        }

        bytes_have_send += temp;
        bytes_to_send -= temp;

        if(bytes_have_send > m_iv[0].iov_len){
            m_iv[0].iov_len = 0;
            m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);
            m_iv[1].iov_len = bytes_to_send;
        }else{
            m_iv[0].iov_base = m_write_buf + bytes_have_send;
            m_iv[0].iov_len -= temp;
        }

        if(bytes_to_send <= 0){
            //没有数据要发了
            unmap();
            modfd(m_epollfd, m_sockfd, EPOLLIN);

            if(m_linger){
                init();
                return true;
            }else{
                return false;
            }
        }


    }
}
//释放内存映射
void http_conn::unmap(){
    if(m_file_address){
        munmap(m_file_address, m_file_stat.st_size);
        m_file_address = 0;
    }
}


//解析HTTP请求
http_conn::HTTP_CODE http_conn::process_read(){

    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char* text = 0;
    while (((m_check_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))
                || ((line_status = parse_line()) == LINE_OK)) {
        // 解析到了一行完整的数据,或者解析到了请求体,也是完整的数据
        // 获取一行数据
        text = get_line();
        // 当前正在解析的行的起始位置就是当前正在分析的字符在读缓冲区中的位置
        m_start_line = m_check_index;
        printf( "got 1 http line: %s\n", text );

        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 do_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请求行,获得请求方法,目标URL,HTTP版本
http_conn::HTTP_CODE http_conn::parse_request_line(char * text){
    
    //  GET /index.html HTTP/1.1
    m_url = strpbrk(text, " \t");   // 判断第二个参数中的字符哪个在text中最先出现
    
    // GET\0/index.html HTTP/1.1
    *m_url++ = '\0'; // 置位空字符,\0为字符串结束符

    char * method = text;
    //  strcasecmp方法比较成功了是返回0
    if( strcasecmp(method, "GET") == 0){    
        m_method = GET;
    }else{
        return BAD_REQUEST;
    }
    //  /index.html HTTP/1.1
    m_version = strpbrk(m_url, " \t");
    if(!m_version){
        return BAD_REQUEST;
    }
    //  /index.html\0HTTP/1.1
    *m_version++ = '\0';
    if(strcasecmp(m_version, "HTTP/1.1") != 0){
        return BAD_REQUEST;
    }
    //只比较7个字符,因为有的可能是http://192.168.110.129:10000/index.html
    if(strncasecmp(m_url, "http://", 7) == 0){
        m_url += 7;
        m_url = strchr(m_url, '/');   // 找到m_url中第一次出现'/'的位置,返回的是指针。
    }

    if(!m_url || m_url[0] != '/'){
        return BAD_REQUEST;
    }

    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请求有消息体,则还需要读取m_content_length字节的消息体
        //状态机转移到CHECK_STATE_CONTENT
        if(m_content_length != 0){
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        //否则就说明我们已经得到了一个完整的HTTP请求
        return GET_REQUEST;
     }else if( strncasecmp(text, "Connection:", 11) == 0){
        //处理Connection头部字段, Connection:keep-alive
        text += 11;
        //strspn(text, " \t") 表示从text开始有连续多少个字符在" \t"能找到
        //比如说Connection:keep-alive
        //在"Connection:" 和 "keep-alive" 可能会有多个空格或者tab,需要跳过这些。
        text += strspn(text, " \t"); 
        if( strcasecmp(text, "keep-alive") == 0 ){
            m_linger = true;
        }
     }else if( strncasecmp (text, "Content-Length:", 15) == 0){
        // 处理Content-Length头部字段
        text += 15;
        text += strspn(text, " \t");
        m_content_length = atol(text);
     }else if( strncasecmp (text, "Host:", 5) == 0){
        // 处理Host头部字段
        text += 5;
        text += strspn(text, "\t");
        m_host = text;
     }else{
        printf("oop! unknow 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_check_index) ){
        text[m_content_length] = '\0';
        return GET_REQUEST;
    }
    return NO_REQUEST;
}
//解析一行,判断依据是\r\n
http_conn::LINE_STATUS http_conn::parse_line(){

    char temp;
    // m_read_idx 在read()函数里读取用户信息以后才会增加,
    for(; m_check_index < m_read_idx; ++m_check_index){
        temp = m_read_buf[m_check_index];
        if( temp == '\r' ){
            if( m_check_index + 1 == m_read_idx){
                return LINE_OPEN;
            }else if(m_read_buf[m_check_index + 1] == '\n'){
                m_read_buf[m_check_index++] = '\0';
                m_read_buf[m_check_index++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }else if(temp == '\n'){
            if((m_check_index > 1) && (m_read_buf[m_check_index - 1] == '\r')){
                m_read_buf[m_check_index - 1] = '\0';
                m_read_buf[m_check_index ++ ] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    //前面都未返回,说明数据不完整
    return LINE_OPEN;
}
// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request(){
    // "/home/nowcoder/webserver1/resources"
    strcpy(m_real_file, doc_root);
    int len = strlen(doc_root);
    strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);
    //获取m_real_file文件的相关的状态信息,-1失败,0成功
    if(stat(m_real_file, &m_file_stat) < 0){
        return NO_RESOURCE;
    }

    //判断访问权限,如果没有权限就返回FORBIDDEN_REQUEST
    if( !(m_file_stat.st_mode & S_IROTH) ){
        return FORBIDDEN_REQUEST;
    }

    //判断是否是目录,如果是目录则返回BAD_REQUEST
    if( S_ISDIR( m_file_stat.st_mode ) ){
        return BAD_REQUEST;
    }

    //以只读方式打开文件,这个文件就是浏览器请求的文件
    int fd = open(m_real_file, O_RDONLY);
    //创建内存映射,这个内存映射会在process_write()函数和write()函数中会用到,用以写出给浏览器
    m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    return FILE_REQUEST;

}


// 根据服务器处理HTTP请求的结果,决定返回给客户端的内容
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 );
            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;
            //m_write_idx是请求行和请求头的大小,
            //m_file_stat.st_size是请求的文件内容的的大小,在这里就是index.html的内容大小
            bytes_to_send = m_write_idx + m_file_stat.st_size;

            return true;
        default:
            return false;
    }

    m_iv[ 0 ].iov_base = m_write_buf;
    m_iv[ 0 ].iov_len = m_write_idx;
    m_iv_count = 1;
    bytes_to_send = m_write_idx;
    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_content_type();
    add_linger();
    add_blank_line();
    return true;
}

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::add_content_type() {
    return add_response("Content-Type:%s\r\n", "text/html");
}

// 往写缓冲中写入返回的请求行和请求头
bool http_conn::add_response( const char* format, ... ) {
    if( m_write_idx >= WRITE_BUFFER_SIZE ) {
        return false;
    }
    // 定义一具VA_LIST型的变量,这个变量是指向参数的指针;
    va_list arg_list;
    //初始化变量刚定义的VA_LIST变量
    va_start( arg_list, format );
    //将arg_list指针所指的参数,按照format格式赋值给m_write_buf数组 ,起点偏移m_write_idx,复制长度为WRITE_BUFFER_SIZE - 1 - m_write_idx
    //如果成功,返回值就是写入缓存区的字节长度
    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宏结束可变参数的获取。
    va_end( arg_list );
    return true;
}


// 由线程池中的工作线程调用,这是处理HTTP请求的入口函数
void http_conn::process(){

    //解析HTTP请求
    HTTP_CODE read_ret = process_read();
    if(read_ret == NO_REQUEST){
        //重置该sockfd的EPOLLIN | EPOLLONESHOT 实践,继续监听
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        return;
    }
    
    //printf("parse request, creat response\n");

    // 生成响应信息,这些信息会在调用write()函数时被写给浏览器
    bool write_ret = process_write( read_ret );
    //如果没有成功,那我就直接关闭连接,因为只有成功了才有后面的写回操作
    if( !write_ret ){
        close_conn();
    }
    modfd( m_epollfd, m_sockfd, EPOLLOUT);
} 

threadpool.h

#ifndef THREADPOOL_H
#define THREADPOOL_H

#include <pthread.h>
#include <list>
#include "locker.h"
#include <cstdio>
#include <exception>
using namespace std;

//线程池,定义为模板类是为了代码的复用
template <typename T>
class threadpool
{

public:
    /*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/
    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;

    //线程池数组,大小为m_thread_number;
    pthread_t * m_threads;

    //请求队列中最多允许的等待处理的请求数量
    int m_max_requests;

    //请求队列
    list< T*> m_workqueue;

    //保护请求队列的互斥锁
    locker m_queuelocker;

    //信号量用来判断是否有任务需要处理;
    sem m_queuestat;

    //是否结束线程
    bool m_stop;


};

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

    if(m_thread_number <= 0 || m_max_requests <= 0){
        throw exception();
    }
    
    //创建线程
    m_threads = new pthread_t[m_thread_number];
    if(!m_threads){
        throw exception();
    }

    //创建thread_number个线程,并将他们设置为线程脱离
    for(int i = 0; i < thread_number; i++){

        printf("create the %dth request\n", i);

        if( pthread_create(m_threads + i, NULL, worker, this) != 0){
            delete [] m_threads;
            throw exception();
        }
        //功能:分离所有创建的线程。被分离的线程在终止的时候,会自动释放资源返回给系统。
        if( pthread_detach(m_threads[i]) ){
            delete [] m_threads;
            throw exception();
        }
    }

}

template<typename T>
threadpool<T>::~threadpool(){
    delete[] m_threads;
    m_stop = true;
}

//append函数是向请求队列添加请求,所以没执行一次就需要信号量加1
template<typename T>
bool threadpool<T>::append(T* request){

    // 上锁,因为它被所有线程共享。
    m_queuelocker.lock();
    if(m_workqueue.size() > m_max_requests){
        m_queuelocker.unlock();
        throw exception();
    }
    //向用户队列中添加用户
    m_workqueue.push_back(request);
    //信号量+1,run()函数中线程发现有用户来了,就开始进行处理。
    m_queuestat.post();
    m_queuelocker.unlock();
    
    return true;

}

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

//run()函数就是处理操作,所以循环一次就需要信号量减1
template<typename T>
void threadpool<T>::run(){
    //当m_stop为false,说明线程在运行,这点很重要,只要线程未结束,就会不停的检测
    while(!m_stop){
        //如果-1后信号量为0,就会阻塞在这里,等待用户请求使信号量+1 才会被激活
        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

locker.h

#ifndef LOCKER_H
#define LOCKER_H

#include <exception>
#include <pthread.h>
#include <semaphore.h>
using namespace std;

//线程同步机制封装类

//互斥锁类
class locker{
private:
    pthread_mutex_t m_mutex;
public:

    locker(){
        if(pthread_mutex_init(&m_mutex, NULL) != 0){
            throw exception();
        }
    }

    ~locker(){
        pthread_mutex_destroy(&m_mutex);
    }

    //上锁
    bool lock(){
        return pthread_mutex_lock(&m_mutex);
    }

    //解锁
    bool unlock(){
        return pthread_mutex_unlock(&m_mutex);
    }

    //获取互斥锁量
    pthread_mutex_t * get(){
        return &m_mutex;
    }
};

//条件变量类
class cond{
private:
    pthread_cond_t m_cond;
public:
    cond(){
        if(pthread_cond_init(&m_cond, NULL) != 0){
            throw exception();
        }
    }

    ~cond(){
        pthread_cond_destroy(&m_cond);
    }

    bool wait(pthread_mutex_t * mutex){
        return pthread_cond_wait(&m_cond, mutex) == 0;
    }

    bool timedwait(pthread_mutex_t * mutex, struct timespec t){
        return pthread_cond_timedwait(&m_cond, mutex, &t) == 0;
    }

    bool signal(pthread_mutex_t * mutex){
        return pthread_cond_signal(&m_cond) == 0;
    }

    bool broadcast(pthread_mutex_t * mutex){
        return pthread_cond_broadcast(&m_cond) == 0;
    }

};

//信号量类
class sem{
private:
    sem_t m_sem;
public:
    sem(){
        if( sem_init(&m_sem, 0, 0) != 0){
            throw exception();
        }
    }

    sem(int num){
        if( sem_init(&m_sem, 0, num) != 0){
            throw exception();
        }
    }

    ~sem(){
        sem_destroy(&m_sem);
    }

    bool wait(){
        return sem_wait(&m_sem) == 0;
    }

    bool post(){
        return sem_post(&m_sem) == 0;
    }
};



#endif
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值