“Web 服务器” 笔记00 ------ 代码

这篇博客详细记录了构建Web服务器的过程,从locker.h和pthreadpool.h的使用开始,逐步深入到http_conn.h和http_conn.cpp的实现,包括笔记02至04中的main.cpp文件改动。博主通过一步步的代码讲解,展示了如何搭建和优化服务器,最后提到了包含1个image图片和1个index.html文件的Resource部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1、locker.h

2、pthreadpool.h

3、“Web 服务器” 笔记02

3.1  main.cpp

3.2  http_conn.h

3.3  http_conn.cpp

4、“Web 服务器” 笔记03

4.1 main.cpp 

4.2 http_conn.h

4.3 http_conn.cpp

5、“Web 服务器” 笔记04

5.1 main.cpp

5.2 http_conn.h

5.3 http_conn.cpp

5.4 Resource


1、locker.h

#ifndef LOCKER_H
#define LOCKER_H
/********************* 封装线程同步的类和头文件 ********************/

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

// 互斥锁类
class locker{
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) == 0;
    }

    bool trylock(){
        return pthread_mutex_trylock(&m_mutex) == 0;
    }

    bool unlock(){
        return pthread_mutex_unlock(&m_mutex) == 0;
    }
private:
    pthread_mutex_t m_mutex;
};

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

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

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

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

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

    bool broadcast(){
        return pthread_cond_broadcast(&m_cond) == 0;
    }
private:
    pthread_cond_t m_cond;
};

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

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

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

    bool trywait(){
        return sem_trywait(&m_sem) == 0;
    }

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

#endif

2、pthreadpool.h

#ifndef PTHREADPOOL_H
#define PTHREADPOOL_H
/******************** 封装线程池类头文件 *******************/

#include "locker.h"
#include <list>
#include <iostream>
using namespace std;


template <typename T>
class pthreadpool{
public:
    pthreadpool(int workqueue_number = 10000, int pthread_number = 8);
    ~pthreadpool();
    // 向工作队列中添加任务
    bool append(T* request);
    static void* worker(void* arg);

private:
    // 线程池运行函数
    void run();
private:
    // 工作队列最大容量
    int m_workqueue_number;

    // 工作队列
    list<T*> m_workqueue;

    // 工作线程最大数量
    int m_pthread_number;

    // 线程池数组
    pthread_t * m_pthreads;

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

    // 工作队列互斥锁
    locker workqueuelocker;

    // 是否停止线程
    bool m_stop;
};

template <typename T>
pthreadpool<T>::pthreadpool(int workqueue_number, int pthread_number) :    // 初始化
        m_workqueue_number(workqueue_number), m_pthread_number(pthread_number),
        m_stop(false), m_pthreads(NULL){
    
    // 创建线程池数组
    m_pthreads = new pthread_t[m_pthread_number];

    // 创建工作线程,并设置线程分离
    for(int i = 0; i < m_pthread_number; i++){
        if( pthread_create(m_pthreads + i, NULL, worker, this) == 0 ){
            cout << "creat " << i <<"th pthread" << endl; 
            // printf("创建第%d个线程\n",i);
            pthread_detach(m_pthreads[i]);
        }
    }
}

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

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

template <typename T>
void pthreadpool<T>::run(){
    while( !m_stop ){
        workqueuetat.wait();
        workqueuelocker.lock();
        if( !m_workqueue.empty() ){
            // 工作队列中有任务
            T* request = m_workqueue.front();
            m_workqueue.pop_front();
            workqueuelocker.unlock();
            // 处理任务函数
            request->process();
        }
    }
}

template <typename T>
bool pthreadpool<T>::append(T* request){
    // 工作队列会被所有线程共享,所以要加锁
    workqueuelocker.lock();
    if( m_workqueue.size() >= m_workqueue_number ){
        // 工作队列已满
        workqueuelocker.unlock();
        printf("工作队列已满!");
        return false;
    }
    // 将任务插入到工作队列中,并且信号量+1
    m_workqueue.push_back(request);
    workqueuelocker.unlock();
    workqueuetat.post();
    return true;
}


#endif

3、“Web 服务器” 笔记02

3.1  main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include "pthreadpool.h"
#include "http_conn.h"
#include <signal.h>

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


int main( int argc, char* argv[] ){

    // 获取端口号
    int port = atoi(argv[1]);

    // 对SIGPIPE信号进行处理
    struct sigaction m_sa;
    m_sa.sa_flags = 0;
    m_sa.sa_handler = SIG_IGN;
    sigfillset( &m_sa.sa_mask );  // 将临时阻塞信号集中的所有的标志位置为1
    sigaction(SIGPIPE, &m_sa, NULL);

    // 创建线程池
    pthreadpool<http_conn>* pool = new pthreadpool<http_conn>;
    // pthreadpool<http_conn>* pool = NULL;

    // 创建一个数组,用于保存所有客户端信息
    http_conn * users = new http_conn[MAX_FD];

    // 创建监听的套接字
    //int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);

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

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

    // 监听
    listen(listenfd, 5);

    // 创建epoll实例,并将监听的文件描述符放入epoll实例中
    int epollfd = epoll_create(5);  // 所有的连接文件描述符共享一个 epollfd
    http_conn::m_epollfd = epollfd;
    struct epoll_event epev;
    epev.events = EPOLLIN | EPOLLRDHUP;
    epev.data.fd = listenfd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &epev);

    // 创建epoll_event数组,将发生了变化的文件描述符放入其中
    struct epoll_event epevs[MAX_EVENT_NUMBER];

    while(1){

        int number = epoll_wait(epollfd, (epoll_event *)&epevs, MAX_FD, -1);

        // 循环遍历epoll_event数组
        for(int i=0; i<number; i++){
            
            int sockfd = epevs[i].data.fd;
            if(sockfd == listenfd){
                // 有客户连接进来
                struct sockaddr_in client_address;
                int client_address_len = sizeof(client_address);
                int connfd = accept(listenfd, (struct sockaddr*)&client_address, (socklen_t *)&client_address_len);

                if(http_conn::m_user_count > MAX_FD){
                    // 用户连接已满
                    close(connfd);
                    continue;
                }

                // 将新客户的信息初始化,放到users数组中
                users[connfd].init(connfd);

            }else if(epevs[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)){
                // 客户端异常断开或者错误等事件发生,关闭连接
                users[sockfd].close_conn();
            }else if(epevs[i].events & EPOLLIN){
                // 读数据
                printf("读取客户端%d\n",sockfd);
                if(users[sockfd].read()){
                    // 一次性把所有数据都读完,将任务添加到工作队列中
                    pool->append(users + sockfd);
                }else{
                    users[sockfd].close_conn();
                }
            }else if(epevs[i].events & EPOLLOUT){
                // 写数据
                // 一次性把数据都写完,关闭文件描述符
                if(users[sockfd].write()){
                    users[sockfd].close_conn();
                }
            }
        }
    }

    close(epollfd);
    close(listenfd);
    delete [] users;
    delete pool;

    return 0;

}

3.2  http_conn.h

#ifndef HTTP_CONN_H
#define HTTP_CONN_H

#include <stdio.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>


class http_conn{
public:
    static int m_user_count;  // 用户数量
    static int m_epollfd;     // epoll实例的文件描述符

    http_conn(){}
    ~http_conn(){}

    void process();     // 处理客户端请求
    void init(int sockfd);  // 初始化新接收的客户端
    bool read();        // 一次性把数据都读完
    bool write();       // 一次性把数据都写完  

    int m_sockfd;       // 连接的文件描述符
    void close_conn();  // 关闭连接

private:
    

};


#endif

3.3  http_conn.cpp

#include "http_conn.h"

// 初始化
int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;

// 关闭连接
void http_conn::close_conn(){
    // 即从epoll实例中删除该连接的文件描述符
    epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_sockfd, 0);
    close(m_sockfd);
    m_sockfd = -1;
    http_conn::m_user_count -=1;
}

// 初始化新接收的客户端
void http_conn::init(int sockfd){

    printf("有新的客户端%d连接进来了\n",sockfd);

    http_conn::m_sockfd = sockfd;
    // 端口复用
    int optval = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    // 将连接的客户端的文件描述符放到epoll实例中
    struct epoll_event epev;
    epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
    // epev.events = EPOLLIN | EPOLLRDHUP;
    epev.data.fd = m_sockfd;
    epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_sockfd, &epev);

    m_user_count += 1;  // 总用户数+1
}

// 一次性把数据都读完
bool http_conn::read(){
    printf("一次性读完数据\n");
    return true;
}

// 一次性把数据都写完
bool http_conn::write(){
    printf("一次性写完数据\n");
    return true;
}

// 工作线程去处理HTTP请求的的入口代码
void http_conn::process(){
    // 解析HTTP请求
    printf("解析HTTP请求,并生成响应\n");

    // 响应HTTP请求
    //printf("响应HTTP请求");
}

4、“Web 服务器” 笔记03

4.1 main.cpp 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include "pthreadpool.h"
#include "http_conn.h"
#include <signal.h>

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


int main( int argc, char* argv[] ){

    // 获取端口号
    int port = atoi(argv[1]);

    // 对SIGPIPE信号进行处理
    struct sigaction m_sa;
    m_sa.sa_flags = 0;
    m_sa.sa_handler = SIG_IGN;
    sigfillset( &m_sa.sa_mask );  // 将临时阻塞信号集中的所有的标志位置为1
    sigaction(SIGPIPE, &m_sa, NULL);

    // 创建线程池
    pthreadpool<http_conn>* pool = new pthreadpool<http_conn>;
    // pthreadpool<http_conn>* pool = NULL;

    // 创建一个数组,用于保存所有客户端信息
    http_conn * users = new http_conn[MAX_FD];

    // 创建监听的套接字
    //int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    // printf("listenfd:%d\n",listenfd);

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

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

    // 监听
    listen(listenfd, 5);

    // 创建epoll实例,并将监听的文件描述符放入epoll实例中
    int epollfd = epoll_create(5);  // 所有的连接文件描述符共享一个 epollfd
    http_conn::m_epollfd = epollfd;
    struct epoll_event epev;
    epev.events = EPOLLIN | EPOLLRDHUP;
    epev.data.fd = listenfd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &epev);
    // printf("epollfd:%d\n",epollfd);

    // 创建epoll_event数组,将发生了变化的文件描述符放入其中
    struct epoll_event epevs[MAX_EVENT_NUMBER];

    while(true){

        int number = epoll_wait(epollfd, (epoll_event *)&epevs, MAX_FD, -1);
        // printf("%d\n",number);

        // 循环遍历epoll_event数组
        for(int i=0; i<number; i++){
            
            int sockfd = epevs[i].data.fd;
            if(sockfd == listenfd){
                // 有客户连接进来
                struct sockaddr_in client_address;
                int client_address_len = sizeof(client_address);
                int connfd = accept(listenfd, (struct sockaddr*)&client_address, (socklen_t *)&client_address_len);
                printf("有新的客户端%d连接进来了\n",connfd);

                if(http_conn::m_user_count > MAX_FD){
                    // 用户连接已满
                    close(connfd);
                    continue;
                }

                // 将新客户的信息初始化,放到users数组中
                users[connfd].init(connfd);

            }else if(epevs[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)){
                // 客户端异常断开或者错误等事件发生,关闭连接
                users[sockfd].close_conn();
            }else if(epevs[i].events & EPOLLIN){
                // 读数据
                printf("读取客户端%d\n",sockfd);
                if(users[sockfd].read()){
                    // 一次性把所有数据都读完,将任务添加到工作队列中
                    pool->append(users + sockfd);
                }else{
                    users[sockfd].close_conn();
                }
            }else if(epevs[i].events & EPOLLOUT){
                // 写数据
                // 一次性把数据都写完,关闭文件描述符
                if(users[sockfd].write()){
                    users[sockfd].close_conn();
                }
            }
        }
    }

    close(epollfd);
    close(listenfd);
    delete [] users;
    delete pool;

    return 0;

}

4.2 http_conn.h

#ifndef HTTP_CONN_H
#define HTTP_CONN_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define READ_BUF_SIZE 2048  // 读缓冲大小
#define FILE_LEN 200        // 文件路径长度


class http_conn{
public:
    static int m_user_count;  // 用户数量
    static int m_epollfd;     // epoll实例的文件描述符

    // 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 };

    http_conn(){}
    ~http_conn(){}

    void process();              // 处理客户端请求
    void init(int sockfd);       // 初始化新接收的客户端
    void init();                 // 初始化新接收的客户端的其他信息
    bool read();                 // 一次性把数据都读完
    bool write();                // 一次性把数据都写完  
    int setnonblocking(int fd);  // 设置文件描述符为非阻塞

    int m_sockfd;       // 连接的文件描述符
    void close_conn();  // 关闭连接
    void modfd();       // 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发

private:
    char* read_buf;       // 读缓冲
    int m_read_index;     // 指向每次向读缓冲读入数据后的下一个位置
    int startline;        // 获取行数据时,每行的起始位置
    int m_checked_index;  // 当前正在分析的字符在读缓冲区中的位置
    METHOD m_method;      // 请求报文首行,方式
    char* m_url;          // 请求报文首行,目标URL(统一资源定位器)
    char* m_version;      // 请求报文首行,版本号
    int m_content_length; // 请求报文头部,请求体长度
    bool m_linger;        // 请求报文头部,是否连接
    char* m_host;         // 主机名


    HTTP_CODE process_read();  // 解析HTTP请求
    char* getline();           // 从读缓冲中获取一行数据
    LINE_STATUS parse_line();  // 解析一行,返回这行数据的完整性
    HTTP_CODE parse_request_line(char* text);  // 解析请求行
    HTTP_CODE parse_headers(char* text);       // 解析请求头部
    HTTP_CODE parse_content(char* text);       // 解析请求体
    HTTP_CODE do_request();                    // 

    CHECK_STATE m_checked_state;  // 主状态机的状态
    char m_real_file[FILE_LEN];   // 客户请求的目标文件的完整路径,其内容等于 doc_root + m_url, doc_root是网站根目录
    struct stat m_file_stat;      // 目标文件的状态。通过它我们可以判断文件是否存在、是否为目录、是否可读,并获取文件大小等信息
    char* m_file_address;         // 客户请求的目标文件被mmap到内存中的起始位置

};


#endif

4.3 http_conn.cpp

#include "http_conn.h"

// 初始化
int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;

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

// 设置文件描述符非阻塞
int http_conn::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;
}

// 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
void http_conn::modfd(){
    struct epoll_event epev;
    epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
    epev.data.fd = m_sockfd;
    epoll_ctl(m_epollfd, EPOLL_CTL_MOD, m_sockfd, &epev);
}  

// 关闭连接
void http_conn::close_conn(){
    // 即从epoll实例中删除该连接的文件描述符
    epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_sockfd, 0);
    close(m_sockfd);
    m_sockfd = -1;
    http_conn::m_user_count -=1;
}

// 初始化新接收的客户端
void http_conn::init(int sockfd){

    // printf("有新的客户端%d连接进来了\n",sockfd);

    http_conn::m_sockfd = sockfd;
    // 端口复用
    int optval = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    // 将连接的客户端的文件描述符放到epoll实例中
    struct epoll_event epev;
    epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
    // epev.events = EPOLLIN | EPOLLRDHUP;
    epev.data.fd = m_sockfd;
    epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_sockfd, &epev);
    setnonblocking(m_sockfd);

    m_user_count += 1;  // 总用户数+1
    init();
}

// 初始化新接收的客户端的其它信息
void http_conn::init(){
    read_buf = new char[READ_BUF_SIZE]; 
    m_read_index = 0;
    startline = 0;
    m_checked_state = CHECK_STATE_REQUESTLINE;
    m_checked_index = 0;

    m_method = GET;  // 默认请求方式为GET
    m_url = 0;
    m_version = 0;

    m_content_length = 0;
    m_linger = false;
    m_host = 0;

    memset(m_real_file, 0, FILE_LEN);
}

// 一次性把数据都读完
bool http_conn::read(){
    int read_bytes = 0;
    while(1){
        read_bytes = recv(m_sockfd, read_buf + m_read_index, sizeof(read_buf), 0);
        if(read_bytes == -1){
            if(errno == EAGAIN | EWOULDBLOCK){
                // recv把fd中的数据读完,放到读缓冲去中
                break;
            }
            return false;
        }else if(read_bytes == 0){
            // 对面关闭连接
            return false;
        }
        m_read_index = m_read_index + read_bytes;
    }
    return true;
}

// 一次性把数据都写完
bool http_conn::write(){
    printf("一次性写完数据\n");
    return true;
}

// 从读缓冲中获取一行数据
char* http_conn::getline(){
    return read_buf + startline;
}

// 解析一行,返回这行数据的完整性,判断依据是/r/n
http_conn::LINE_STATUS http_conn::parse_line(){
    for(; m_checked_index < m_read_index; m_checked_index++){
        if(read_buf[m_checked_index] == '\r'){
            if((m_checked_index + 1) == m_read_index){
                return LINE_OPEN;
            }else if(read_buf[m_checked_index + 1] == '\n'){
                read_buf[m_checked_index] = '\0';      // 要将字符串获取到,\0为字符串结束符
                read_buf[m_checked_index + 1] = '\0';  // \r\n 变为 \0\0
                m_checked_index += 1;  // 下一次分析字符时的位置
                return LINE_OK;
            }
            return LINE_BAD;
        }
        if(read_buf[m_checked_index] == '\n'){
            if(m_checked_index > 1 && read_buf[m_checked_index - 1] == '\r'){
                read_buf[m_checked_index - 1] == '\0';
                read_buf[m_checked_index] == '\0';
                m_checked_index += 1;
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

// 解析请求行
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
    // GET /index.html HTTP/1.1
    m_url = strpbrk(text, " \t");
    if(!m_url) return BAD_REQUEST;
    *m_url++ = '\0';   // GET\0/index.html HTTP/1.1
    if(strcasecmp(text, "GET") == 0){
        m_method = GET;
    }else{
        return BAD_REQUEST;
    }

    char* version;
    version = strpbrk(m_url, " \t");  // m_url为/index.html HTTP/1.1
    *version++ = '\0';  // /index.html\0HTTP/1.1
    if(strcasecmp(version, "HTTP/1.1") == 0){
        m_version = "HTTP/1.1";
    }else{
        return BAD_REQUEST;
    }

    /*  m_url 可能是下面的两种:
        http://192.168.110.129:10000/index.html
        或者
        /index.html
    */
    if(strncasecmp(m_url, "http://", 7) == 0){
        m_url += 7;
        m_url = strchr(m_url, '/');  // m_url为/index.html
        if(m_url[0] != '/' || !m_url){
            return BAD_REQUEST;
        }
    }
    m_checked_state = CHECK_STATE_HEADER;
    // 请求报文的请求行中方法、目标URL和版本号都正确解析,下一步该解析请求首部了
    return NO_REQUEST;
}

// 解析请求头部
http_conn::HTTP_CODE http_conn::parse_headers(char* text){
    // 遇到空行表示请求头部已经解析完成
    if(text[0] == '\0'){
        if(m_content_length == 0){
            // 如果请求报文没有消息体,则表示获得一个完整的客户请求
            return GET_REQUEST;
        }else{
            // 如果请求报文有消息体,那么还要继续解析消息体
            m_checked_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
    }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, "Connection:", 11) == 0){
        // 处理Connection头部字段,keep-alive
        text += 11;
        text += strspn(text, " \t");
        if(strcasecmp(text, "keep-alive") == 0){
            m_linger = true;
        }
    }else if(strncasecmp(text, "Host:", 5) == 0){
        // 处理Host头部字段
        text += 5;
        text += strspn(text, " \t");
        m_host = text;
    }else{
        // 咱们定义之外的其他头部字段
        printf("Oh,我也不知道是啥头部字段 %s",text);
    }
    return NO_REQUEST;
}

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

// 解析HTTP请求
http_conn::HTTP_CODE http_conn::process_read(){
    char* text = 0;
    HTTP_CODE ret = NO_REQUEST;
    LINE_STATUS line_status = LINE_OK;
    while((line_status = parse_line()) == LINE_OK || 
    ((m_checked_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))){
        // 读取到一个完整的行
        text = getline();
        startline = m_checked_index;
        printf("得到一行数据:%s\n",text);

        switch(m_checked_state){
            case CHECK_STATE_REQUESTLINE:{
                // 解析请求行
                if(parse_request_line(text) == 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:{
                // 解析请求主体
                if(parse_content(text) == GET_REQUEST){
                    return do_request();
                }
            }
            default:{
                return INTERNAL_ERROR;
            }
        }
    }
    return NO_REQUEST;
}

// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request() 
{
    // "/home/nowcoder/Practice/resources"
    strcpy( m_real_file, doc_root );
    int len = strlen( doc_root );
    strncpy( m_real_file + len, m_url, FILE_LEN - len - 1 );
    // 获取m_real_file文件的相关的状态信息,-1失败,0成功
    if ( stat( m_real_file, &m_file_stat ) < 0 ) {
        return NO_RESOURCE;
    }

    // 判断访问权限
    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)
    m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
    close(fd);
    return FILE_REQUEST;
}

// 工作线程去处理HTTP请求的的入口代码
void http_conn::process(){
    // 解析HTTP请求
    HTTP_CODE read_ret = process_read();
    if( read_ret == NO_REQUEST){
        // HTTP请求不完整,需要继续读取客户数据
        // 重置epolloneshot事件
        modfd();
        return;
    }

    // 响应HTTP请求
    //printf("响应HTTP请求");
}

5、“Web 服务器” 笔记04

5.1 main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include "pthreadpool.h"
#include "http_conn.h"
#include <signal.h>

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


int main( int argc, char* argv[] ){

    // 获取端口号
    int port = atoi(argv[1]);

    // 对SIGPIPE信号进行处理
    struct sigaction m_sa;
    m_sa.sa_flags = 0;
    m_sa.sa_handler = SIG_IGN;
    sigfillset( &m_sa.sa_mask );  // 将临时阻塞信号集中的所有的标志位置为1
    sigaction(SIGPIPE, &m_sa, NULL);

    // 创建线程池
    pthreadpool<http_conn>* pool = new pthreadpool<http_conn>;
    // pthreadpool<http_conn>* pool = NULL;

    // 创建一个数组,用于保存所有客户端信息
    http_conn * users = new http_conn[MAX_FD];

    // 创建监听的套接字
    //int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    // printf("listenfd:%d\n",listenfd);

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

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

    // 监听
    listen(listenfd, 5);

    // 创建epoll实例,并将监听的文件描述符放入epoll实例中
    int epollfd = epoll_create(5);  // 所有的连接文件描述符共享一个 epollfd
    http_conn::m_epollfd = epollfd;
    struct epoll_event epev;
    epev.events = EPOLLIN | EPOLLRDHUP;
    epev.data.fd = listenfd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &epev);
    // printf("epollfd:%d\n",epollfd);

    // 创建epoll_event数组,将发生了变化的文件描述符放入其中
    struct epoll_event epevs[MAX_EVENT_NUMBER];

    while(true){

        int number = epoll_wait(epollfd, (epoll_event *)&epevs, MAX_FD, -1);
        // printf("%d\n",number);

        // 循环遍历epoll_event数组
        for(int i=0; i<number; i++){
            
            int sockfd = epevs[i].data.fd;
            if(sockfd == listenfd){
                // 有客户连接进来
                struct sockaddr_in client_address;
                int client_address_len = sizeof(client_address);
                int connfd = accept(listenfd, (struct sockaddr*)&client_address, (socklen_t *)&client_address_len);
                printf("有新的客户端%d连接进来了\n",connfd);

                if(http_conn::m_user_count > MAX_FD){
                    // 用户连接已满
                    close(connfd);
                    continue;
                }

                // 将新客户的信息初始化,放到users数组中
                users[connfd].init(connfd);

            }else if(epevs[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)){
                // 客户端异常断开或者错误等事件发生,关闭连接
                users[sockfd].close_conn();
            }else if(epevs[i].events & EPOLLIN){
                // 读数据
                printf("读取客户端%d\n",sockfd);
                if(users[sockfd].read()){
                    // 一次性把所有数据都读完,将任务添加到工作队列中
                    pool->append(users + sockfd);
                }else{
                    users[sockfd].close_conn();
                }
            }else if(epevs[i].events & EPOLLOUT){
                // 写数据
                // 一次性把数据都写完,关闭文件描述符
                if(users[sockfd].write()){
                    users[sockfd].close_conn();
                }
            }
        }
    }

    close(epollfd);
    close(listenfd);
    delete [] users;
    delete pool;

    return 0;

}

5.2 http_conn.h

#ifndef HTTP_CONN_H
#define HTTP_CONN_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <sys/uio.h>
#include <unistd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define READ_BUF_SIZE 2048   // 读缓冲大小
#define FILE_LEN 200         // 文件路径长度
#define WRITE_BUF_SIZE 2048  // 写缓冲大小


class http_conn{
public:
    static int m_user_count;  // 用户数量
    static int m_epollfd;     // epoll实例的文件描述符

    // 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 };

    http_conn(){}
    ~http_conn(){}

    void process();              // 处理客户端请求
    void init(int sockfd);       // 初始化新接收的客户端
    void init();                 // 初始化新接收的客户端的其他信息
    bool read();                 // 一次性把数据都读完
    bool write();                // 一次性把数据都写完  
    int setnonblocking(int fd);  // 设置文件描述符为非阻塞

    int m_sockfd;       // 连接的文件描述符
    void close_conn();  // 关闭连接
    void modfd(int ep);       // 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发

private:
    char* read_buf;       // 读缓冲
    int m_read_index;     // 指向每次向读缓冲读入数据后的下一个位置
    int startline;        // 获取行数据时,每行的起始位置
    int m_checked_index;  // 当前正在分析的字符在读缓冲区中的位置
    METHOD m_method;      // 请求报文首行,方式
    char* m_url;          // 请求报文首行,目标URL(统一资源定位器)
    char* m_version;      // 请求报文首行,版本号
    int m_content_length; // 请求报文头部,请求体长度
    bool m_linger;        // 请求报文头部,是否连接
    char* m_host;         // 主机名


    HTTP_CODE process_read();  // 解析HTTP请求
    char* getline();           // 从读缓冲中获取一行数据
    LINE_STATUS parse_line();  // 解析一行,返回这行数据的完整性
    HTTP_CODE parse_request_line(char* text);  // 解析请求行
    HTTP_CODE parse_headers(char* text);       // 解析请求头部
    HTTP_CODE parse_content(char* text);       // 解析请求体
    HTTP_CODE do_request();                    
    void unmap();                              // 对内存映射区执行munmap操作

    CHECK_STATE m_checked_state;  // 主状态机的状态
    char m_real_file[FILE_LEN];   // 客户请求的目标文件的完整路径,其内容等于 doc_root + m_url, doc_root是网站根目录
    struct stat m_file_stat;      // 目标文件的状态。通过它我们可以判断文件是否存在、是否为目录、是否可读,并获取文件大小等信息
    char* m_file_address;         // 客户请求的目标文件被mmap到内存中的起始位置

    bool process_write(HTTP_CODE read_ret);              // 生成HTTP响应
    bool add_status_line(int status,const char* title);  // 添加响应首行
    bool add_headers(int len);                           // 添加响应头部
    bool add_content(const char* form);                  // 添加响应体
    bool add_response(const char* farmat, ...);          //往读缓冲中写入待发送的数据
    bool add_content_length(int len);                    // 添加响应头中的Content-Length
    bool add_content_type();                             // 添加响应头中的Content-Type
    bool add_linger();                                   // 添加响应头中的Connection
    bool add_blank_line();                               // 添加空行

    char* write_buf;         // 写缓冲
    int m_write_index;       // 指向每次向写缓冲写入数据后的下一个位置

    struct iovec io_vec[2];     // 我们将采用writev来执行写操作,所以定义下面两个成员,其中m_io_count表示被写内存块的数量。
    int m_io_count;

    int bytes_to_send;    // 将要发送的字节数
    int bytes_have_sent;  // 已经发送的字节数
};


#endif

5.3 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_user_count = 0;
int http_conn::m_epollfd = -1;

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

// 设置文件描述符非阻塞
int http_conn::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;
}

// 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
void http_conn::modfd(int ep){
    struct epoll_event epev;
    epev.events = ep | EPOLLRDHUP | EPOLLONESHOT;
    epev.data.fd = m_sockfd;
    epoll_ctl(m_epollfd, EPOLL_CTL_MOD, m_sockfd, &epev);
}  

// 关闭连接
void http_conn::close_conn(){
    // 即从epoll实例中删除该连接的文件描述符
    epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_sockfd, 0);
    close(m_sockfd);
    m_sockfd = -1;
    http_conn::m_user_count -=1;
}

// 初始化新接收的客户端
void http_conn::init(int sockfd){

    // printf("有新的客户端%d连接进来了\n",sockfd);

    http_conn::m_sockfd = sockfd;
    // 端口复用
    int optval = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    // 将连接的客户端的文件描述符放到epoll实例中
    struct epoll_event epev;
    epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
    // epev.events = EPOLLIN | EPOLLRDHUP;
    epev.data.fd = m_sockfd;
    epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_sockfd, &epev);
    setnonblocking(m_sockfd);

    m_user_count += 1;  // 总用户数+1
    init();
}

// 初始化新接收的客户端的其它信息
void http_conn::init(){
    read_buf = new char[READ_BUF_SIZE]; 
    write_buf = new char[WRITE_BUF_SIZE];
    m_read_index = 0;
    startline = 0;
    m_checked_state = CHECK_STATE_REQUESTLINE;
    m_checked_index = 0;

    m_method = GET;  // 默认请求方式为GET
    m_url = 0;
    m_version = 0;

    m_content_length = 0;
    m_linger = false;
    m_host = 0;

    m_file_address = 0;
    m_write_index = 0;

    memset(read_buf, 0, READ_BUF_SIZE);
    memset(m_real_file, 0, FILE_LEN);
    memset(write_buf, 0, WRITE_BUF_SIZE);

    m_io_count = 0;
    bytes_to_send = 0;    
    bytes_have_sent = 0;  
}

// 一次性把数据都读完
bool http_conn::read(){
    int read_bytes = 0;
    while(1){
        read_bytes = recv(m_sockfd, read_buf + m_read_index, sizeof(read_buf), 0);
        if(read_bytes == -1){
            if(errno == EAGAIN | EWOULDBLOCK){
                // recv把fd中的数据读完,放到读缓冲去中
                break;
            }
            return false;
        }else if(read_bytes == 0){
            // 对面关闭连接
            return false;
        }
        m_read_index = m_read_index + read_bytes;
    }
    return true;
}

// 从读缓冲中获取一行数据
char* http_conn::getline(){
    return read_buf + startline;
}

// 解析一行,返回这行数据的完整性,判断依据是/r/n
http_conn::LINE_STATUS http_conn::parse_line(){
    for(; m_checked_index < m_read_index; m_checked_index++){
        if(read_buf[m_checked_index] == '\r'){
            if((m_checked_index + 1) == m_read_index){
                return LINE_OPEN;
            }else if(read_buf[m_checked_index + 1] == '\n'){
                read_buf[m_checked_index] = '\0';      // 要将字符串获取到,\0为字符串结束符
                read_buf[m_checked_index + 1] = '\0';  // \r\n 变为 \0\0
                m_checked_index += 1;  // 下一次分析字符时的位置
                return LINE_OK;
            }
            return LINE_BAD;
        }
        if(read_buf[m_checked_index] == '\n'){
            if(m_checked_index > 1 && read_buf[m_checked_index - 1] == '\r'){
                read_buf[m_checked_index - 1] == '\0';
                read_buf[m_checked_index] == '\0';
                m_checked_index += 1;
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

// 解析请求行
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
    // GET /index.html HTTP/1.1
    m_url = strpbrk(text, " \t");
    if(!m_url) return BAD_REQUEST;
    *m_url++ = '\0';   // GET\0/index.html HTTP/1.1
    if(strcasecmp(text, "GET") == 0){
        m_method = GET;
    }else{
        return BAD_REQUEST;
    }

    char* version;
    version = strpbrk(m_url, " \t");  // m_url为/index.html HTTP/1.1
    *version++ = '\0';  // /index.html\0HTTP/1.1
    if(strcasecmp(version, "HTTP/1.1") == 0){
        m_version = "HTTP/1.1";
    }else{
        return BAD_REQUEST;
    }

    /*  m_url 可能是下面的两种:
        http://192.168.110.129:10000/index.html
        或者
        /index.html
    */
    if(strncasecmp(m_url, "http://", 7) == 0){
        m_url += 7;
        m_url = strchr(m_url, '/');  // m_url为/index.html
        if(m_url[0] != '/' || !m_url){
            return BAD_REQUEST;
        }
    }
    m_checked_state = CHECK_STATE_HEADER;
    // 请求报文的请求行中方法、目标URL和版本号都正确解析,下一步该解析请求首部了
    return NO_REQUEST;
}

// 解析请求头部
http_conn::HTTP_CODE http_conn::parse_headers(char* text){
    // 遇到空行表示请求头部已经解析完成
    if(text[0] == '\0'){
        if(m_content_length == 0){
            // 如果请求报文没有消息体,则表示获得一个完整的客户请求
            return GET_REQUEST;
        }else{
            // 如果请求报文有消息体,那么还要继续解析消息体
            m_checked_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
    }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, "Connection:", 11) == 0){
        // 处理Connection头部字段,keep-alive
        text += 11;
        text += strspn(text, " \t");
        if(strcasecmp(text, "keep-alive") == 0){
            m_linger = true;
        }
    }else if(strncasecmp(text, "Host:", 5) == 0){
        // 处理Host头部字段
        text += 5;
        text += strspn(text, " \t");
        m_host = text;
    }else{
        // 咱们定义之外的其他头部字段
        printf("Oh,我也不知道是啥头部字段 %s",text);
    }
    return NO_REQUEST;
}

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

// 解析HTTP请求
http_conn::HTTP_CODE http_conn::process_read(){
    char* text = 0;
    HTTP_CODE ret = NO_REQUEST;
    LINE_STATUS line_status = LINE_OK;
    while((line_status = parse_line()) == LINE_OK || 
    ((m_checked_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))){
        // 读取到一个完整的行
        text = getline();
        startline = m_checked_index;
        printf("得到一行数据:%s\n",text);

        switch(m_checked_state){
            case CHECK_STATE_REQUESTLINE:{
                // 解析请求行
                if(parse_request_line(text) == 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:{
                // 解析请求主体
                if(parse_content(text) == GET_REQUEST){
                    return do_request();
                }
            }
            default:{
                return INTERNAL_ERROR;
            }
        }
    }
    return NO_REQUEST;
}

// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request() 
{
    // "/home/nowcoder/Practice/resources"
    strcpy( m_real_file, doc_root );
    int len = strlen( doc_root );
    strncpy( m_real_file + len, m_url, FILE_LEN - len - 1 );
    // 获取m_real_file文件的相关的状态信息,-1失败,0成功
    if ( stat( m_real_file, &m_file_stat ) < 0 ) {
        return NO_RESOURCE;
    }

    // 判断访问权限
    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)
    m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
    close(fd);
    return FILE_REQUEST;
}

// 往读缓冲中写入待发送的数据
bool http_conn::add_response(const char* format, ...){
    if(m_write_index > WRITE_BUF_SIZE){
        return false;
    }
    va_list elem;
    va_start(elem, format);
    int len = vsnprintf(write_buf + m_write_index, WRITE_BUF_SIZE - m_write_index , format, elem);
    if(len>WRITE_BUF_SIZE - m_write_index){
        return false;
    }
    m_write_index += len;
    va_end(elem);
    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 len){
    add_content_length(len);
    add_content_type();
    add_linger();
    add_blank_line();
}

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

bool http_conn::add_content_type(){
    return add_response("Content-Type: %s\r\n", "text/HTML");
}

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* form){
    return add_response("%s", form);
}

// 对内存映射区执行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;
    if(bytes_to_send == 0){
        // 将要发送的字节数为0,这一次响应结束
        modfd(EPOLLIN);
        init();
        return true;
    }
     while(1) {
        // 分散写
        temp = writev(m_sockfd, io_vec, m_io_count);
        if ( temp <= -1 ) {
            // 如果TCP写缓冲没有空间,则等待下一轮EPOLLOUT事件,虽然在此期间,
            // 服务器无法立即接收到同一客户的下一个请求,但可以保证连接的完整性。
            if( errno == EAGAIN ) {
                modfd(EPOLLOUT);
                return true;
            }
            unmap();
            return false;
        }

        bytes_have_sent += temp;
        bytes_to_send -= temp;

        if (bytes_have_sent >= io_vec[0].iov_len)
        {
            io_vec[0].iov_len = 0;
            io_vec[1].iov_base = m_file_address + (bytes_have_sent - m_write_index);
            io_vec[1].iov_len = bytes_to_send;
        }
        else
        {
            io_vec[0].iov_base = write_buf + bytes_have_sent;
            io_vec[0].iov_len = io_vec[0].iov_len - temp;
        }

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

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

// 生成HTTP响应
bool http_conn::process_write(HTTP_CODE read_ret){
    switch(read_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_REQUEST:
            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);
            io_vec[0].iov_base = write_buf;           // 写缓冲的首地址
            io_vec[0].iov_len = m_write_index;        // 写缓冲区中待发送的数据长度
            io_vec[1].iov_base = m_file_address;      // 目标文件的首地址
            io_vec[1].iov_len = m_file_stat.st_size;  // 目标文件的大小
            
            m_io_count = 2;
            bytes_to_send = m_write_index + m_file_stat.st_size;
            return true;
        default:{
            return false;
        }
    }

    // 如果报错,那么将writev将发送的数据
    io_vec[0].iov_base = write_buf;
    io_vec[0].iov_len = m_write_index;

    m_io_count = 1;
    bytes_to_send = m_write_index;
    return true;
}

// 工作线程去处理HTTP请求的的入口代码
void http_conn::process(){
    // 解析HTTP请求
    HTTP_CODE read_ret = process_read();
    if( read_ret == NO_REQUEST){
        // HTTP请求不完整,需要继续读取客户数据
        // 重置epolloneshot事件
        modfd(EPOLLIN);
        return;
    }

    // 响应HTTP请求
    bool write_ret = process_write(read_ret);
    if(!write_ret){
        // 如果报错,关闭连接
        close_conn();
    }
    modfd(EPOLLOUT);
}

5.4 Resource

1个 image 图片 + 1个index.html 文件。 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
</head>
<body>

    <h1>欢迎来到首页~~~</h1> </br>
    <img src="images/image1.jpg"  alt="ces" />
    
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值