用C++实现的高性能WEB服务器

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

一、创建好运行环境

二、编译过程

三、makefile 文件

四、gdb调试过程

这个别人的文章也说的很多了,无非是生成可调试文件,然后打断点调试什么的。

五、静态库和动态库

六、一些概念

七、TCP通信模型

八、reactor模式

九、具体代码分析

动态空间分配类

线程池类

HTTP请求类 

http_response

httpconn

epoll

计时器

Webserver

总结


简单记录一下学习过程

一、创建好运行环境

搞了一下午,直接打包好一个文件了

创建免密登录的步骤

在windows操作台上输入

ssh-keygen -t rsa

在C盘中找到相应的公钥文件,并复制

在虚拟机上存放公私钥文件的地方创建

vim authorized_keys

并将复制好的的公钥文件复制进去。

二、编译过程

用C++实现的高性能WEB服务器_第1张图片

预处理就是将注释什么的去掉,并且将宏转化为相应的符号

编译就是生成汇编代码

汇编就是生成机器代码

链接就是将这些机器代码与相应的库资源一起封装打包成可执行的包。

三、makefile 文件

直接在有makefile文件的地方输入make,系统会自动为你生成可执行文件。具体的语法有:

#随便找个栗子说一下   
   OBJS = kang.o yul.o#定义变量 
   CC = Gcc#定义变量
   CFLAGS = -Wall -O -g#定义变量 -Wall 好像是生成警告
   sunq : $(OBJS)#sunq为目标 ,OBJS为所需要的汇编文件
         $(CC) $(OBJS) -o sunq #相当于gcc kang.o yul.o -o sunq
   #由于找不到相应的汇编文件,相应进一步找其c文件,并将其生成 ,下面的类似
   kang.o : kang.c kang.h 
         $(CC) $(CFLAGS) -c kang.c -o kang.o
   yul.o : yul.c yul.h
         $(CC) $(CFLAGS) -c yul.c -o yul.o

四、gdb调试过程

这个别人的文章也说的很多了,无非是生成可调试文件,然后打断点调试什么的。

五、静态库和动态库

静态库就是链接的时候一起绑好,运行的时候一起装入内存中

动态库就是给出该库地址,运行时需要该库在进行调用,它可以给多个运行的程序调用,比较方便剩内存。

静态库的制作

ar rcs libxxx.a xx.o xx.o

动态库的制作

gcc -c -fpic xx.c xx.c //-fpic 得到于位置无关的代码

gcc -shared xx.o xx.o -o  libcal.so

ldd xxx 查找动态库的依赖关系

动态库有时候找不到需要配置环境变量,具体。。。

六、一些概念

僵尸进程:父进程未对子进程资源进行回收

孤儿进程:父进程结束后,Init进程会对其进行回收

匿名管道:只能由有亲缘的进程使用,原因是父进程通过fork()创建的子进程读时共享写时复制,所以它们可以找到同一块匿名区域进行通信。

有名管道:说白了就是创建一个文件,进程间可以通过该文件进行通信

共享内存,内存中创建一块区域来用于通信,速度比较快

端口复用:防止服务器重启时之前绑定的端口还未释放,程序突然退出系统,没有释放端口。

I/O多路转接技术:select/poll:告诉你有几个快递到了,但是哪个快递你需要挨个便利

epoll()多路复用,使用红黑树和双链表来实现。有LT模式:这次没读完下次继续通知你,ET模式,这次没读完数据,下次就不通知了,因此需要用户一次性读完数据,并且设置非阻塞读。

同步异步的区别:数据是有内核帮忙拷贝还是自己的工作进程进行拷贝。

同步IO实现Reactor模式(后面具体讲)

异步IO实现Proactor模式

explicit 指定只能显示转换什么的

定时器:每个进程都有且只有一个定时器。

七、TCP通信模型

用C++实现的高性能WEB服务器_第2张图片

具体流程大致是这样,图画的将就看吧

八、reactor模式

  Reactor 模式也叫做反应器设计模式,是一种为处理服务请求并发提交到一个或者多个服务处理器的事件设计模式。当请求抵达后,通过服务处理器将这些请求采用多路分离的方式分发给相应的请求处理器。Reactor 模式主要由 Reactor 和处理器 Handler 这两个核心部分组成,如下图所示,它俩负责的事情如下:

Reactor:负责监听和分发事件,事件类型包含连接事件、读写事件;
Handler :负责处理事件,如 read -> 业务逻辑 (decode + compute + encode)-> send;

说白了就是使用一个进程来接收socket事件后,在将它们分发给相应的工作进程来处理。

用C++实现的高性能WEB服务器_第3张图片

要求主线程( I/O 处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作
线程(逻辑单元),将 socket 可读可写事件放入请求队列,交给工作线程处理。除此之外,主线程不做
任何其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。
使用同步 I/O (以 epoll_wait 为例)实现的 Reactor 模式的工作流程是:
1. 主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。
2. 主线程调用 epoll_wait 等待 socket 上有数据可读。
3. socket 上有数据可读时, epoll_wait 通知主线程。主线程则将 socket 可读事件放入请求队列。
4. 睡眠在请求队列上的某个工作线程被唤醒,它从 socket 读取数据,并处理客户请求,然后往 epoll
内核事件表中注册该 socket 上的写就绪事件。
5. 当主线程调用 epoll_wait 等待 socket 可写。
6. socket 可写时, epoll_wait 通知主线程。主线程将 socket 可写事件放入请求队列。
7. 睡眠在请求队列上的某个工作线程被唤醒,它往 socket 上写入服务器处理客户请求的结果。
主线程负责分发事件(读事件就将其放入读队列中,写事件就将其放入写队列中,等待工作线程处理)与内核打交道,工作线程负责处理业务逻辑。

九、具体代码分析

整个代码大概理了一下,可以先不看log日志。

首先,初始化webserver类,该类用来初始化各个类并且创建一个监听描述符,随后执行一个start函数,不断while循环接收内核监听的事件。1.当监听到新连接后,则有epller类添加进去。2.当监听到为读事件时,调用httpconn类来执行读取数据,并且修改客户端的监听描述符为写事件,内核会再次通知服务器进行写事件。2.当监听到写事件时,则调用httpconn类来执行写事件,并且修改该客户端的监听描述符为监听读事件,也就是等待客户端进来新的信息。简单来说就是,主线程监听到数据后,线程读出来告诉内核可以写了,内核通知写事件,线程在再进行写处理。

在步骤2中,调用httpconn执行读事件时,httpconn会将其写入动态内存buff中,后告诉内核可以进行写事件了。

在步骤3中,监听到写事件后,httpconn会执行处理,将buff中的数据先通过httprequest类进行解析,再再httpresponse中生成响应返回给客户端。

动态空间分配类

在动态内存中,使用读写指针来动态的移动内存。写空间不够时,可以重新利用已读完的空间,即将它们的指针前移。实在不够内存了则将内存扩充。


#ifndef BUFFER_H
#define BUFFER_H
#include    //perror
#include 
#include   // write
#include  //readv
#include  //readv
#include 
#include 
class Buffer {
public:
    Buffer(int initBuffSize = 1024);//构造函数
    ~Buffer() = default;//析构函数

    size_t WritableBytes() const;   //可写字节    
    size_t ReadableBytes() const ;  //可读字节
    size_t PrependableBytes() const; //预增加可用字节

    const char* Peek() const; //返回读指针位置
    void EnsureWriteable(size_t len);//确保可写
    void HasWritten(size_t len);//写指针后移

    void Retrieve(size_t len);//读完数据后,读指针后移
    void RetrieveUntil(const char* end);//根据终点来后移读指针

    void RetrieveAll() ;//恢复所有
    std::string RetrieveAllToStr();//返回还未读完的字节并恢复所有

    const char* BeginWriteConst() const;//返回写开始位置,不可被更改
    char* BeginWrite();//返回写开始位置

    //将数据拷贝入内存中,从写指针开始
    void Append(const std::string& str);
    void Append(const char* str, size_t len);
    void Append(const void* data, size_t len);
    void Append(const Buffer& buff);
    //读取数据
    ssize_t ReadFd(int fd, int* Errno);
    //写入数据
    ssize_t WriteFd(int fd, int* Errno);

private:
    char* BeginPtr_();//内存开始指针
    const char* BeginPtr_() const;//开始指针 
    void MakeSpace_(size_t len);//制造空间

    std::vector buffer_;//内存空间
    std::atomic readPos_;//可读相对位置
    std::atomic writePos_;//可写相对位置
};

#endif //BUFFER_H
/*
 * @Author       : mark
 * @Date         : 2020-06-26
 * @copyleft Apache 2.0
 */ 
#include "buffer.h"
//初始化,读写指针均为0
Buffer::Buffer(int initBuffSize) : buffer_(initBuffSize), readPos_(0), writePos_(0) {}
//可读字节数,已写位置-读位置,得出还未读的数据
size_t Buffer::ReadableBytes() const {
    return writePos_ - readPos_;
}
//可写字节,空间大小-已写指针
size_t Buffer::WritableBytes() const {
    return buffer_.size() - writePos_;
}
//预可用字节 初始位置0到读指针的空间是已经读取过了,可以重新用
size_t Buffer::PrependableBytes() const {
    return readPos_;
}
//返回读指针位置
const char* Buffer::Peek() const {
    return BeginPtr_() + readPos_;
}
//读完数据后,读指针后移
void Buffer::Retrieve(size_t len) {
    assert(len <= ReadableBytes());
    readPos_ += len;
}
//根据内存位置来后移读指针
void Buffer::RetrieveUntil(const char* end) {
    assert(Peek() <= end );
    Retrieve(end - Peek());
}
//恢复所有
void Buffer::RetrieveAll() {
    bzero(&buffer_[0], buffer_.size());
    readPos_ = 0;
    writePos_ = 0;
}
//返回还未读完的字节并恢复所有
std::string Buffer::RetrieveAllToStr() {
    std::string str(Peek(), ReadableBytes());
    RetrieveAll();
    return str;
}
//返回写开始位置
const char* Buffer::BeginWriteConst() const {
    return BeginPtr_() + writePos_;
}
//返回写开始位置
char* Buffer::BeginWrite() {
    return BeginPtr_() + writePos_;
}
//写指针后移
void Buffer::HasWritten(size_t len) {
    writePos_ += len;
} 
/**
append的四种重载,供ReadFd使用
主要调用第三个:

**/
void Buffer::Append(const std::string& str) {
    Append(str.data(), str.length());
}

void Buffer::Append(const void* data, size_t len) {
    assert(data);
    Append(static_cast(data), len);
}

void Buffer::Append(const char* str, size_t len) {
    assert(str);
    EnsureWriteable(len);//确保可写
    std::copy(str, str + len, BeginWrite());//将str数据拷贝入内存中,从写指针开始
    HasWritten(len);//写指针后移
}

void Buffer::Append(const Buffer& buff) {
    Append(buff.Peek(), buff.ReadableBytes());
}
//如果可写字节小于该长度,则增加空间
void Buffer::EnsureWriteable(size_t len) {
    if(WritableBytes() < len) {
        MakeSpace_(len);
    }
    assert(WritableBytes() >= len);
}
//读取数据
ssize_t Buffer::ReadFd(int fd, int* saveErrno) {
    char buff[65535];
    struct iovec iov[2];
    const size_t writable = WritableBytes();
    /* 分散读, 保证数据全部读完 */
    iov[0].iov_base = BeginPtr_() + writePos_;
    iov[0].iov_len = writable;
    iov[1].iov_base = buff;
    iov[1].iov_len = sizeof(buff);

    const ssize_t len = readv(fd, iov, 2);//返回读取长度
    if(len < 0) {
        *saveErrno = errno;
    }
    else if(static_cast(len) <= writable) {//该内存位置可存入
        writePos_ += len;
    }
    else {//该内存位置不够存入
        writePos_ = buffer_.size();
        Append(buff, len - writable);
    }
    return len;
}
//写入数据
ssize_t Buffer::WriteFd(int fd, int* saveErrno) {
    size_t readSize = ReadableBytes();//可读字节长度
    ssize_t len = write(fd, Peek(), readSize);
    if(len < 0) {
        *saveErrno = errno;
        return len;
    } 
    readPos_ += len;
    return len;
}
//返回内存开始指针
char* Buffer::BeginPtr_() {
    return &*buffer_.begin();
}

const char* Buffer::BeginPtr_() const {
    return &*buffer_.begin();
}
//增加空间
void Buffer::MakeSpace_(size_t len) {
    if(WritableBytes() + PrependableBytes() < len) {
        buffer_.resize(writePos_ + len + 1);
    } 
    else {
        size_t readable = ReadableBytes();
        std::copy(BeginPtr_() + readPos_, BeginPtr_() + writePos_, BeginPtr_());
        readPos_ = 0;
        writePos_ = readPos_ + readable;
        assert(readable == ReadableBytes());
    }
}

加了一些注释,buffer主要是用来动态分配内存来存储和读取数据。通过读指针和写指针得移动来有效得利用空间内存,如预可读空间返回读指针,说明从0到读指针位置已经读过了,此空间可以重新使用。使用得时候,可以调用MakeSpace_来将读指针和未读数据前移,对准初始位置0.

线程池类

线程池类,打工人类。该类一旦被初始化后,就创建了好几个线程,执行线程执行while死循环。不断的通过从任务对类中获取任务来执行任务。其中,还使用了锁来实现信息同步。


#ifndef THREADPOOL_H
#define THREADPOOL_H

#include 
#include 
#include 
#include 
#include 
class ThreadPool {
public:
    /*
          线程池类初始化,使用初值赋予可以减少值得拷贝次数
    */
    //explicit指定构造函数或转换函数 (C++11 起) 或推导指南 (C++17 起) 是显式的,也就是说,它不能用于隐式转换和复制初始化。
    explicit ThreadPool(size_t threadCount = 8): pool_(std::make_shared()) {
            assert(threadCount > 0);
            //创建threadCount个线程
            for(size_t i = 0; i < threadCount; i++) {

                std::thread([pool = pool_] {//匿名函数,输入变量pool
                    std::unique_lock locker(pool->mtx);//锁管理模板,创建时自动加锁
                    while(true) {
                        if(!pool->tasks.empty()) {
                            //取出任务
                            auto task = std::move(pool->tasks.front());
                            pool->tasks.pop();
                            locker.unlock();//解锁
                            task();//执行任务
                            locker.lock();//加锁
                        } 
                        else if(pool->isClosed) break;//池是否关闭
                        else pool->cond.wait(locker);//等待条件变量唤醒
                    }
                }).detach();//设置线程分离
            }
    }

    ThreadPool() = default;

    ThreadPool(ThreadPool&&) = default;
    //析构函数
    ~ThreadPool() {
        if(static_cast(pool_)) {
            {
                //lock_grard可以保证锁一定解除并且删除
                std::lock_guard locker(pool_->mtx);
                pool_->isClosed = true;
            }
            pool_->cond.notify_all();
        }
    }
    /*
      模板类
    */
    template
    void AddTask(F&& task) {
        {   /*
               lock_guard:
               没有提供加锁和解锁的接口。
               通过构造函数和析构函数控制锁的作用范围,
               创造对象的时候加锁,离开作用域的时候解锁;
            */
            std::lock_guard locker(pool_->mtx);
            /*
            std::forward通常是用于完美转发的,它会将输入的参数原封不动地传递到下一个函数中,
            这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;
            如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。
            */
            pool_->tasks.emplace(std::forward(task));
        }
        pool_->cond.notify_one();//唤醒一个条件变量
    }

private:
    /*
      池类
      私有类,把继承的类定义为私有的类
    */
    struct Pool {
        std::mutex mtx;//互斥锁
        std::condition_variable cond;//条件变量
        bool isClosed;//判断是否关闭
        /*
          std::function是C++11标准库中提供的一种可调用对象的通用类型
          ,它可以存储任意可调用对象,如函数指针,函数对象,成员函数指针和lambda表达式。
        */
        std::queue> tasks;//存储需要执行的工作
    };
    std::shared_ptr pool_;//智能指针,创建该类和自动销毁此类,可以被多个对象调用
};


#endif //THREADPOOL_H

HTTP请求类 

解析从buff中获取的数据,得到其http格式


#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H

#include 
#include 
#include 
#include 
#include      
#include   //mysql

#include "../buffer/buffer.h"
#include "../log/log.h"
#include "../pool/sqlconnpool.h"
#include "../pool/sqlconnRAII.h"

class HttpRequest {
public:
    /*
      枚举类型:
    */
    enum PARSE_STATE {
        REQUEST_LINE,//请求行
        HEADERS,//请求头
        BODY,//请求体
        FINISH,//完成        
    };
    /*
      枚举类型:
    */
    enum HTTP_CODE {
        NO_REQUEST = 0,//没有请求
        GET_REQUEST,//得到请求
        BAD_REQUEST,//坏请求
        NO_RESOURSE,//没有请求
        FORBIDDENT_REQUEST,//禁止的请求
        FILE_REQUEST,//文件请求
        INTERNAL_ERROR,//网络错误
        CLOSED_CONNECTION,//关闭连接
    };
    
    HttpRequest() { Init(); }//构造函数
    ~HttpRequest() = default;//析构函数

    void Init();//初始化
    bool parse(Buffer& buff);//解析处理

    std::string path() const;//路径
    std::string& path();//路径
    std::string method() const;//方法
    std::string version() const;//版本
    std::string GetPost(const std::string& key) const;//得到post
    std::string GetPost(const char* key) const;//得到post

    bool IsKeepAlive() const;//是否活着

    /* 
    todo 
    void HttpConn::ParseFormData() {}
    void HttpConn::ParseJson() {}
    */

private:
    bool ParseRequestLine_(const std::string& line);//处理请求行
    void ParseHeader_(const std::string& line);//处理请求头
    void ParseBody_(const std::string& line);//处理请求体

    void ParsePath_();//处理请求路径
    void ParsePost_();//处理Post事件
    void ParseFromUrlencoded_();//从url中解析编码

    //用户验证
    static bool UserVerify(const std::string& name, const std::string& pwd, bool isLogin);
    
    PARSE_STATE state_;//解析状态:枚举类型
    std::string method_, path_, version_, body_;//方法,路径,版本,结构
    std::unordered_map header_;//头
    std::unordered_map post_;//请求

    static const std::unordered_set DEFAULT_HTML;//默认HTML集合
    static const std::unordered_map DEFAULT_HTML_TAG;//默认htmltag
    static int ConverHex(char ch);//转换为16进制
};


#endif //HTTP_REQUEST_H

#include "httprequest.h"
using namespace std;

//初始化无序集合  DEFAULT_HTML并赋值
const unordered_set HttpRequest::DEFAULT_HTML{
            "/index", "/register", "/login",
             "/welcome", "/video", "/picture", };
//初始化无序映射  DEFAULT_HTML_TAG并赋值
const unordered_map HttpRequest::DEFAULT_HTML_TAG {
            {"/register.html", 0}, {"/login.html", 1},  };
//初始化
void HttpRequest::Init() {
    method_ = path_ = version_ = body_ = "";
    state_ = REQUEST_LINE;
    header_.clear();
    post_.clear();
}
//是否保持活着
bool HttpRequest::IsKeepAlive() const {
    //
    if(header_.count("Connection") == 1) {//字典序中有“Connection”
        return header_.find("Connection")->second == "keep-alive" && version_ == "1.1";
    }
    //返回错误
    return false;
}
/*
  解析
  输入:内存块

*/
bool HttpRequest::parse(Buffer& buff) {
    const char CRLF[] = "\r\n";
    //没有可读字节
    if(buff.ReadableBytes() <= 0) {
        return false;
    }
    //读取数据
    while(buff.ReadableBytes() && state_ != FINISH) {
        //从buff的读指针开始到开始写指针结束,这块区域是未读取的数据并去除"\r\n",返回有效数据的行末指针
        const char* lineEnd = search(buff.Peek(), buff.BeginWriteConst(), CRLF, CRLF + 2);
        //转化未string类型
        std::string line(buff.Peek(), lineEnd);
        switch(state_)
        {
        /*
          有限状态机,从请求行开始,每处理完后会自动转入到下一个状态    
        */
        case REQUEST_LINE:
            //调用解析请求行
            if(!ParseRequestLine_(line)) {
                return false;
            }
            //解析路径
            ParsePath_();
            break;    
        case HEADERS:
            //解析请求头
            ParseHeader_(line);
            if(buff.ReadableBytes() <= 2) {
                state_ = FINISH;
            }
            break;
        case BODY:
            //解析请求体
            ParseBody_(line);
            break;
        default:
            break;
        }
        //如果读完了,结束
        if(lineEnd == buff.BeginWrite()) { break; }
        //根据终点来后移读指针
        buff.RetrieveUntil(lineEnd + 2);
    }
    LOG_DEBUG("[%s], [%s], [%s]", method_.c_str(), path_.c_str(), version_.c_str());
    return true;
}
/*
  解析路径
*/
void HttpRequest::ParsePath_() {
    if(path_ == "/") {
        path_ = "/index.html"; 
    }
    else {
        for(auto &item: DEFAULT_HTML) {
            if(item == path_) {
                path_ += ".html";
                break;
            }
        }
    }
}
/*
  解析请求行
*/
bool HttpRequest::ParseRequestLine_(const string& line) {
    regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
    smatch subMatch;
    if(regex_match(line, subMatch, patten)) {   
        method_ = subMatch[1];
        path_ = subMatch[2];
        version_ = subMatch[3];
        state_ = HEADERS;//状态转为下一个状态
        return true;
    }
    LOG_ERROR("RequestLine Error");
    return false;
}
/*
  解析请求头
*/
void HttpRequest::ParseHeader_(const string& line) {
    regex patten("^([^:]*): ?(.*)$");
    smatch subMatch;
    if(regex_match(line, subMatch, patten)) {//将匹配到的数据转化在subMatch中
        header_[subMatch[1]] = subMatch[2];//将键值对放入map数组中
    }
    else {
        state_ = BODY;//状态转为下一个状态
    }
}
/*
  解析请求体
*/
void HttpRequest::ParseBody_(const string& line) {
    body_ = line;
    ParsePost_();
    state_ = FINISH;//状态转为下一个状态
    LOG_DEBUG("Body:%s, len:%d", line.c_str(), line.size());
}
/*
  16进制转十进制
*/
int HttpRequest::ConverHex(char ch) {
    if(ch >= 'A' && ch <= 'F') return ch -'A' + 10;
    if(ch >= 'a' && ch <= 'f') return ch -'a' + 10;
    return ch;
}
/*
  处理post请求
*/
void HttpRequest::ParsePost_() {
    //方法类型是“POST”并且"Content-Type"是"application/x-www-form-urlencoded"
    if(method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") {
        ParseFromUrlencoded_();
        if(DEFAULT_HTML_TAG.count(path_)) {//字典中有找到path_,path_定义在头文件中,在这个ParseRequestLine_函数中赋值
            int tag = DEFAULT_HTML_TAG.find(path_)->second;//path_的键值对
            LOG_DEBUG("Tag:%d", tag);
            if(tag == 0 || tag == 1) {
                bool isLogin = (tag == 1);//tag=1为登录,否则为0
                //用户验证,请求路径改变
                if(UserVerify(post_["username"], post_["password"], isLogin)) {
                    path_ = "/welcome.html";
                } 
                else {
                    path_ = "/error.html";
                }
            }
        }
    }   
}
//从url中解析编码
void HttpRequest::ParseFromUrlencoded_() {
    if(body_.size() == 0) { return; }

    string key, value;
    int num = 0;
    int n = body_.size();
    int i = 0, j = 0;
    //解析请求体
    for(; i < n; i++) {
        char ch = body_[i];
        switch (ch) {
        case '=':
            key = body_.substr(j, i - j);
            j = i + 1;
            break;
        case '+':
            body_[i] = ' ';
            break;
        case '%':
            num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]);
            body_[i + 2] = num % 10 + '0';
            body_[i + 1] = num / 10 + '0';
            i += 2;
            break;
        case '&':
            value = body_.substr(j, i - j);
            j = i + 1;
            post_[key] = value;
            LOG_DEBUG("%s = %s", key.c_str(), value.c_str());
            break;
        default:
            break;
        }
    }
    assert(j <= i);
    if(post_.count(key) == 0 && j < i) {
        value = body_.substr(j, i - j);
        post_[key] = value;
    }
}
//用户登录验证
bool HttpRequest::UserVerify(const string &name, const string &pwd, bool isLogin) {
    if(name == "" || pwd == "") { return false; }//没有用户名或密码,返回错误
    LOG_INFO("Verify name:%s pwd:%s", name.c_str(), pwd.c_str());
    MYSQL* sql;
    SqlConnRAII(&sql,  SqlConnPool::Instance());//创建sql连接,智能释放
    assert(sql);
    
    bool flag = false;//用户名是否已经使用
    unsigned int j = 0;//查询结果行的数量
    char order[256] = { 0 };//存储sql语句
    MYSQL_FIELD *fields = nullptr;//存储查询结果文件
    MYSQL_RES *res = nullptr;//查询结果集
    //如果不是登录行为,用户名设置为未使用
    if(!isLogin) { flag = true; }
    /* 查询用户及密码 并将该语句存储在order中*/
    snprintf(order, 256, "SELECT username, password FROM user WHERE username='%s' LIMIT 1", name.c_str());
    LOG_DEBUG("%s", order);
    //在sql中查询该语句,查询语句执行成功则返回0,否则则释放查询结果内存
    if(mysql_query(sql, order)) { 
        mysql_free_result(res);
        return false; 
    }
    res = mysql_store_result(sql);//将查询的全部结果读取到客户端,分配一个Mysql_res结构,并将结果置于该结构中
    j = mysql_num_fields(res);//结果集中行的数量
    fields = mysql_fetch_fields(res);//来获取结果集中的文件
     
    while(MYSQL_ROW row = mysql_fetch_row(res)) {//获取结果集中的行
        LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);
        string password(row[1]);
        /* 登录行为 且 密码正确*/
        if(isLogin) {
            if(pwd == password) { flag = true; }
            else {
                flag = false;
                LOG_DEBUG("pwd error!");
            }
        } 
        else { 
            flag = false; 
            LOG_DEBUG("user used!");
        }
    }
    mysql_free_result(res);//释放结果集内存

    /* 注册行为 且 用户名未被使用*/
    if(!isLogin && flag == true) {
        LOG_DEBUG("regirster!");
        bzero(order, 256);
        snprintf(order, 256,"INSERT INTO user(username, password) VALUES('%s','%s')", name.c_str(), pwd.c_str());
        LOG_DEBUG( "%s", order);
        if(mysql_query(sql, order)) { 
            LOG_DEBUG( "Insert error!");
            flag = false; 
        }
        flag = true;
    }
    SqlConnPool::Instance()->FreeConn(sql);//从数据库中释放该连接
    //用户创建的连接自动释放
    LOG_DEBUG( "UserVerify success!!");
    return flag;
}

std::string HttpRequest::path() const{
    return path_;
}

std::string& HttpRequest::path(){
    return path_;
}
std::string HttpRequest::method() const {
    return method_;
}

std::string HttpRequest::version() const {
    return version_;
}

std::string HttpRequest::GetPost(const std::string& key) const {
    assert(key != "");
    if(post_.count(key) == 1) {
        return post_.find(key)->second;
    }
    return "";
}

std::string HttpRequest::GetPost(const char* key) const {
    assert(key != nullptr);
    if(post_.count(key) == 1) {
        return post_.find(key)->second;
    }
    return "";
}

http_response

获取解析后解析相应的响应。生成响应文件


#ifndef HTTP_RESPONSE_H
#define HTTP_RESPONSE_H

#include 
#include        // open
#include       // close
#include     // stat
#include     // mmap, munmap

#include "../buffer/buffer.h"
#include "../log/log.h"

class HttpResponse {
public:
    HttpResponse();//构造函数
    ~HttpResponse();//析构函数
    //初始化
    void Init(const std::string& srcDir, std::string& path, bool isKeepAlive = false, int code = -1);
    //做出响应
    void MakeResponse(Buffer& buff);
    //释放文件内存
    void UnmapFile();
    //文件
    char* File();
    //文件长度
    size_t FileLen() const;
    //错误内容
    void ErrorContent(Buffer& buff, std::string message);
    //编码
    int Code() const { return code_; }

private:
    void AddStateLine_(Buffer &buff);//添加状态行
    void AddHeader_(Buffer &buff);//添加头文件
    void AddContent_(Buffer &buff);//添加内容

    void ErrorHtml_();//错误网页
    std::string GetFileType_();//获取文件类型

    int code_;//状态编码
    bool isKeepAlive_;//是否还连接着

    std::string path_;//路径
    std::string srcDir_;//源目录
    
    char* mmFile_;//文件 
    struct stat mmFileStat_;//文件数据格式

    static const std::unordered_map SUFFIX_TYPE;//后缀类型集
    static const std::unordered_map CODE_STATUS;//编码状态集
    static const std::unordered_map CODE_PATH;//编码路径集
};


#endif //HTTP_RESPONSE_H

#include "httpresponse.h"

using namespace std;
//后缀类型键值对
const unordered_map HttpResponse::SUFFIX_TYPE = {
    { ".html",  "text/html" },
    { ".xml",   "text/xml" },
    { ".xhtml", "application/xhtml+xml" },
    { ".txt",   "text/plain" },
    { ".rtf",   "application/rtf" },
    { ".pdf",   "application/pdf" },
    { ".word",  "application/nsword" },
    { ".png",   "image/png" },
    { ".gif",   "image/gif" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".au",    "audio/basic" },
    { ".mpeg",  "video/mpeg" },
    { ".mpg",   "video/mpeg" },
    { ".avi",   "video/x-msvideo" },
    { ".gz",    "application/x-gzip" },
    { ".tar",   "application/x-tar" },
    { ".css",   "text/css "},
    { ".js",    "text/javascript "},
};
//状态码,键值对
const unordered_map HttpResponse::CODE_STATUS = {
    { 200, "OK" },
    { 400, "Bad Request" },
    { 403, "Forbidden" },
    { 404, "Not Found" },
};
//错误编码路径,键值对
const unordered_map HttpResponse::CODE_PATH = {
    { 400, "/400.html" },
    { 403, "/403.html" },
    { 404, "/404.html" },
};

//赋值初始化
HttpResponse::HttpResponse() {
    code_ = -1;
    path_ = srcDir_ = "";
    isKeepAlive_ = false;
    mmFile_ = nullptr; 
    mmFileStat_ = { 0 };
};
//析构函数
HttpResponse::~HttpResponse() {
    UnmapFile();
}
//初始化
void HttpResponse::Init(const string& srcDir, string& path, bool isKeepAlive, int code){
    assert(srcDir != "");
    if(mmFile_) { UnmapFile(); }//如果这块内存有东西,则把它上面的东西删除掉
    code_ = code;
    isKeepAlive_ = isKeepAlive;
    path_ = path;
    srcDir_ = srcDir;
    mmFile_ = nullptr; 
    mmFileStat_ = { 0 };
}

void HttpResponse::MakeResponse(Buffer& buff) {
    /* 判断请求的资源文件 */
     /*将路径文件中的数据信息放入mmFileStat
     一般在之前都会先调用函数stat( FileName, &fp),意味着将FileName这个文件的信息保存到了地址fp中。此时fp.st_mode就是文件FileName的模式,所以S_ISDIR(fp.st_mode)的函数功能是判断fp所指向文件(也就是FileName)是否为目录(dir)类型。
    */
    if(stat((srcDir_ + path_).data(), &mmFileStat_) < 0 || S_ISDIR(mmFileStat_.st_mode)) {
        code_ = 404;
    }
   /*
     S_IRUSR:用户读权限
     S_IWUSR:用户写权限
     S_IRGRP:用户组读权限
     S_IWGRP:用户组写权限
     S_IROTH:其他组读权限
     S_IWOTH:其他组写权限
   */
    else if(!(mmFileStat_.st_mode & S_IROTH)) {
        code_ = 403;
    }
    else if(code_ == -1) { 
        code_ = 200; 
    }
    ErrorHtml_();
    AddStateLine_(buff);
    AddHeader_(buff);
    AddContent_(buff);
}
/*
  返回文件
*/
char* HttpResponse::File() {
    return mmFile_;
}
/*
  返回文件长度
*/
size_t HttpResponse::FileLen() const {
    return mmFileStat_.st_size;
}

void HttpResponse::ErrorHtml_() {
    /*
      在错误码中有找到的话
    */
    if(CODE_PATH.count(code_) == 1) {
        path_ = CODE_PATH.find(code_)->second;
        stat((srcDir_ + path_).data(), &mmFileStat_);//通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
    }
}
//添加状态行进buff中
void HttpResponse::AddStateLine_(Buffer& buff) {
    string status;
    if(CODE_STATUS.count(code_) == 1) {
        status = CODE_STATUS.find(code_)->second;
    }
    else {
        code_ = 400;
        status = CODE_STATUS.find(400)->second;
    }
    buff.Append("HTTP/1.1 " + to_string(code_) + " " + status + "\r\n");
}
//添加状态头进buff中
void HttpResponse::AddHeader_(Buffer& buff) {
    buff.Append("Connection: ");
    if(isKeepAlive_) {
        buff.Append("keep-alive\r\n");
        buff.Append("keep-alive: max=6, timeout=120\r\n");
    } else{
        buff.Append("close\r\n");
    }
    buff.Append("Content-type: " + GetFileType_() + "\r\n");
}

void HttpResponse::AddContent_(Buffer& buff) {
    //打开文件
    int srcFd = open((srcDir_ + path_).data(), O_RDONLY);
    if(srcFd < 0) { 
        ErrorContent(buff, "File NotFound!");
        return; 
    }
    /* 将文件映射到内存提高文件的访问速度 
        MAP_PRIVATE 建立一个写入时拷贝的私有映射*/
    LOG_DEBUG("file path %s", (srcDir_ + path_).data());
    /*
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    prot参数有四种取值:
        PROT_EXEC表示映射的这一段可执行,例如映射共享库
        PROT_READ表示映射的这一段可读
        PROT_WRITE表示映射的这一段可写
        PROT_NONE表示映射的这一段不可访问
        lag参数有很多种取值,这里只讲两种,其它取值可查看mmap
        MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,
        另一个进程也会看到这种变化。
        MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,
        另一个进程并不会看到这种变化,也不会真的写到文件中去。
        具体可见:https://blog.csdn.net/bian_cheng_ru_men/article/details/80359938
    */
    int* mmRet = (int*)mmap(0, mmFileStat_.st_size, PROT_READ, MAP_PRIVATE, srcFd, 0);
    if(*mmRet == -1) {
        ErrorContent(buff, "File NotFound!");
        return; 
    }
    mmFile_ = (char*)mmRet;//转换为char* 类型,它的长度为mmFileStat_.st_size
    close(srcFd);
    buff.Append("Content-length: " + to_string(mmFileStat_.st_size) + "\r\n\r\n");
}
/*
  释放内存映射
*/
void HttpResponse::UnmapFile() {
    if(mmFile_) {
        munmap(mmFile_, mmFileStat_.st_size);//释放内存映射
        mmFile_ = nullptr;
    }
}

string HttpResponse::GetFileType_() {
    /* 判断文件类型 */
    string::size_type idx = path_.find_last_of('.');
    if(idx == string::npos) {
        return "text/plain";
    }
    string suffix = path_.substr(idx);
    if(SUFFIX_TYPE.count(suffix) == 1) {
        return SUFFIX_TYPE.find(suffix)->second;
    }
    return "text/plain";
}
/*错误的内容*/
void HttpResponse::ErrorContent(Buffer& buff, string message) 
{
    string body;
    string status;
    body += "Error";
    body += "";
    if(CODE_STATUS.count(code_) == 1) {
        status = CODE_STATUS.find(code_)->second;
    } else {
        status = "Bad Request";
    }
    body += to_string(code_) + " : " + status  + "\n";
    body += "

" + message + "

"; body += "
TinyWebServer"; buff.Append("Content-length: " + to_string(body.size()) + "\r\n\r\n"); buff.Append(body); }

httpconn

进行读写数据并调用httprequest 来解析数据以及httpresponse来生成响应

#ifndef HTTP_CONN_H
#define HTTP_CONN_H

#include 
#include      // readv/writev
#include    // sockaddr_in
#include       // atoi()
#include       

#include "../log/log.h"
#include "../pool/sqlconnRAII.h"
#include "../buffer/buffer.h"
#include "httprequest.h"
#include "httpresponse.h"

class HttpConn {
public:
    HttpConn();//构造函数

    ~HttpConn();//析构函数

    void init(int sockFd, const sockaddr_in& addr);//初始化

    ssize_t read(int* saveErrno);//读

    ssize_t write(int* saveErrno);//写

    void Close();//关闭

    int GetFd() const;//获取文件fd

    int GetPort() const;//获取文件端口

    const char* GetIP() const;//获取ip
    
    sockaddr_in GetAddr() const;//获取地址
    
    bool process();//处理
    //写字节
    int ToWriteBytes() { 
        return iov_[0].iov_len + iov_[1].iov_len; 
    }
    //是否保持活着
    bool IsKeepAlive() const {
        return request_.IsKeepAlive();
    }

    static bool isET;//是否是et模式
    static const char* srcDir;
    static std::atomic userCount;//支持锁
    
private:
   
    int fd_;//文件描述符
    struct  sockaddr_in addr_;//地址

    bool isClose_;//是否关闭
    
    int iovCnt_;
    struct iovec iov_[2];
    
    Buffer readBuff_; // 读缓冲区
    Buffer writeBuff_; // 写缓冲区

    HttpRequest request_;//http请求
    HttpResponse response_;//http响应
};


#endif //HTTP_CONN_H

#ifndef HTTP_CONN_H
#define HTTP_CONN_H

#include 
#include      // readv/writev
#include    // sockaddr_in
#include       // atoi()
#include       

#include "../log/log.h"
#include "../pool/sqlconnRAII.h"
#include "../buffer/buffer.h"
#include "httprequest.h"
#include "httpresponse.h"

class HttpConn {
public:
    HttpConn();//构造函数

    ~HttpConn();//析构函数

    void init(int sockFd, const sockaddr_in& addr);//初始化

    ssize_t read(int* saveErrno);//读

    ssize_t write(int* saveErrno);//写

    void Close();//关闭

    int GetFd() const;//获取文件fd

    int GetPort() const;//获取文件端口

    const char* GetIP() const;//获取ip
    
    sockaddr_in GetAddr() const;//获取地址
    
    bool process();//处理
    //写字节
    int ToWriteBytes() { 
        return iov_[0].iov_len + iov_[1].iov_len; 
    }
    //是否保持活着
    bool IsKeepAlive() const {
        return request_.IsKeepAlive();
    }

    static bool isET;//是否是et模式
    static const char* srcDir;
    static std::atomic userCount;//支持锁
    
private:
   
    int fd_;//文件描述符
    struct  sockaddr_in addr_;//地址

    bool isClose_;//是否关闭
    
    int iovCnt_;
    struct iovec iov_[2];
    
    Buffer readBuff_; // 读缓冲区
    Buffer writeBuff_; // 写缓冲区

    HttpRequest request_;//http请求
    HttpResponse response_;//http响应
};


#endif //HTTP_CONN_H

epoll

监听客户端的连接事件

/*
1.首先调用int epoll_create(int size);创建一个 epoll
2.调用int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
为 epoll 注册事件(如果是新建的 epoll 一般 op 选项是EPOLL_CTL_ADD添加事件)
3.调用int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待事件的到来,得到的结果存储在 event 中
4.完全处理完毕后,再次调用int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);删除已经注册的事件(op 选项是EPOLL_CTL_DEL)

*/

 
#ifndef EPOLLER_H
#define EPOLLER_H

#include  //epoll_ctl()
#include   // fcntl()
#include  // close()
#include  // close()
#include 
#include 

class Epoller {
public:
    /*
      显示定义构造函数,使其不能被默认构造器调用,默认最大监听事件1024
    */
    explicit Epoller(int maxEvent = 1024);
    /*
      析构函数
    */
    ~Epoller();
    /*
      添加fd进event数组
    */
    bool AddFd(int fd, uint32_t events);
    /*
      从events数组中移除fd
    */
    bool ModFd(int fd, uint32_t events);
    /*
      删除fd
    */
    bool DelFd(int fd);

    int Wait(int timeoutMs = -1);

    int GetEventFd(size_t i) const;

    uint32_t GetEvents(size_t i) const;
        
private:
    int epollFd_;//epoll端口

    std::vector events_;   //用来存储拷贝从内核过来的事件 
};

#endif //EPOLLER_H



#include "epoller.h"
//epoll_create()打开一个epoll文件描述符,后面的参数大于0就行
Epoller::Epoller(int maxEvent):epollFd_(epoll_create(512)), events_(maxEvent){
    assert(epollFd_ >= 0 && events_.size() > 0);
}

Epoller::~Epoller() {
    close(epollFd_);
}

bool Epoller::AddFd(int fd, uint32_t events) {
    if(fd < 0) return false;
    epoll_event ev = {0};
    ev.data.fd = fd;
    ev.events = events;
    return 0 == epoll_ctl(epollFd_, EPOLL_CTL_ADD, fd, &ev);//添加入epoll事件组中
}

bool Epoller::ModFd(int fd, uint32_t events) {
    if(fd < 0) return false;
    epoll_event ev = {0};
    ev.data.fd = fd;
    ev.events = events;
    return 0 == epoll_ctl(epollFd_, EPOLL_CTL_MOD, fd, &ev);
}

bool Epoller::DelFd(int fd) {
    if(fd < 0) return false;
    epoll_event ev = {0};
    return 0 == epoll_ctl(epollFd_, EPOLL_CTL_DEL, fd, &ev);
}

int Epoller::Wait(int timeoutMs) {
    //等待epoll文件描述符上的I/O事件。
    return epoll_wait(epollFd_, &events_[0], static_cast(events_.size()), timeoutMs);
}

int Epoller::GetEventFd(size_t i) const {
    assert(i < events_.size() && i >= 0);
    return events_[i].data.fd;
}

uint32_t Epoller::GetEvents(size_t i) const {
    assert(i < events_.size() && i >= 0);
    return events_[i].events;
}

计时器

使用小跟堆来存储各个客户端连接的超时时间,当超时时,则断开该连接


#ifndef HEAP_TIMER_H
#define HEAP_TIMER_H

#include 
#include 
#include 
#include 
#include  
#include  
#include  
#include 
#include "../log/log.h"

typedef std::function TimeoutCallBack;//超时时间函数
typedef std::chrono::high_resolution_clock Clock;//高精度钟,拥有最小系统可提供的时间单位
typedef std::chrono::milliseconds MS;//毫秒
typedef Clock::time_point TimeStamp;//时间点

struct TimerNode {
    int id;
    TimeStamp expires;
    TimeoutCallBack cb;
    bool operator<(const TimerNode& t) {
        return expires < t.expires;
    }
};
class HeapTimer {
public:
    //构造函数
    HeapTimer() { heap_.reserve(64); }
    //析构函数
    ~HeapTimer() { clear(); }
    //调整新超时时间
    void adjust(int id, int newExpires);
    //添加
    void add(int id, int timeOut, const TimeoutCallBack& cb);
    /* 删除指定id结点,并触发回调函数 */
    void doWork(int id);
    //清除
    void clear();
    
    //清除超时时间
    void tick();
    
    //弹出
    void pop();
    
    
    //获取下一个超时时间
    int GetNextTick();

private:
    void del_(size_t i);
    
    void siftup_(size_t i);

    bool siftdown_(size_t index, size_t n);

    void SwapNode_(size_t i, size_t j);

    std::vector heap_;//小根堆

    std::unordered_map ref_;//索引
};

#endif //HEAP_TIMER_H

#include "heaptimer.h"

void HeapTimer::siftup_(size_t i) {
    //上移节点
    assert(i >= 0 && i < heap_.size());
    size_t j = (i - 1) / 2;
    while(j >= 0) {
        if(heap_[j] < heap_[i]) { break; }
        SwapNode_(i, j);
        i = j;
        j = (i - 1) / 2;
    }
}

void HeapTimer::SwapNode_(size_t i, size_t j) {
    //交换节点
    assert(i >= 0 && i < heap_.size());
    assert(j >= 0 && j < heap_.size());
    std::swap(heap_[i], heap_[j]);
    ref_[heap_[i].id] = i;
    ref_[heap_[j].id] = j;
} 

bool HeapTimer::siftdown_(size_t index, size_t n) {
    //下移节点
    assert(index >= 0 && index < heap_.size());
    assert(n >= 0 && n <= heap_.size());
    size_t i = index;
    size_t j = i * 2 + 1;
    while(j < n) {
        if(j + 1 < n && heap_[j + 1] < heap_[j]) j++;
        if(heap_[i] < heap_[j]) break;
        SwapNode_(i, j);
        i = j;
        j = i * 2 + 1;
    }
    return i > index;
}

void HeapTimer::add(int id, int timeout, const TimeoutCallBack& cb) {
    assert(id >= 0);
    size_t i;
    if(ref_.count(id) == 0) {
        /* 新节点:堆尾插入,调整堆 */
        i = heap_.size();
        ref_[id] = i;
        heap_.push_back({id, Clock::now() + MS(timeout), cb});
        siftup_(i);
    } 
    else {
        /* 已有结点:调整堆 */
        i = ref_[id];
        heap_[i].expires = Clock::now() + MS(timeout);
        heap_[i].cb = cb;
        if(!siftdown_(i, heap_.size())) {
            siftup_(i);
        }
    }
}

void HeapTimer::doWork(int id) {
    /* 删除指定id结点,并触发回调函数 */
    if(heap_.empty() || ref_.count(id) == 0) {
        return;
    }
    size_t i = ref_[id];
    TimerNode node = heap_[i];
    node.cb();
    del_(i);
}

void HeapTimer::del_(size_t index) {
    /* 删除指定位置的结点 */
    assert(!heap_.empty() && index >= 0 && index < heap_.size());
    /* 将要删除的结点换到队尾,然后调整堆 */
    size_t i = index;
    size_t n = heap_.size() - 1;
    assert(i <= n);
    if(i < n) {
        SwapNode_(i, n);
        if(!siftdown_(i, n)) {
            siftup_(i);
        }
    }
    /* 队尾元素删除 */
    ref_.erase(heap_.back().id);
    heap_.pop_back();
}

void HeapTimer::adjust(int id, int timeout) {
    /* 调整指定id的结点 */
    assert(!heap_.empty() && ref_.count(id) > 0);
    heap_[ref_[id]].expires = Clock::now() + MS(timeout);;
    siftdown_(ref_[id], heap_.size());
}

void HeapTimer::tick() {
    /* 清除超时结点 */
    if(heap_.empty()) {
        return;
    }
    while(!heap_.empty()) {
        TimerNode node = heap_.front();
        if(std::chrono::duration_cast(node.expires - Clock::now()).count() > 0) { 
            break; 
        }
        node.cb();
        pop();
    }
}

void HeapTimer::pop() {
    assert(!heap_.empty());
    del_(0);
}

void HeapTimer::clear() {
    ref_.clear();
    heap_.clear();
}
/*
  获取下一个超时时间
*/
int HeapTimer::GetNextTick() {
    tick();
    size_t res = -1;
    if(!heap_.empty()) {
        res = std::chrono::duration_cast(heap_.front().expires - Clock::now()).count();
        if(res < 0) { res = 0; }
    }
    return res;
}

Webserver

主要与内核打交道,监听来自内核的epoll事件,并将写任务放入写任务队列中,读任务放入读任务队列中

#ifndef WEBSERVER_H
#define WEBSERVER_H

#include 
#include        // fcntl()
#include       // close()
#include 
#include 
#include 
#include 
#include 

#include "epoller.h"
#include "../log/log.h"
#include "../timer/heaptimer.h"
#include "../pool/sqlconnpool.h"
#include "../pool/threadpool.h"
#include "../pool/sqlconnRAII.h"
#include "../http/httpconn.h"

class WebServer {
public:
    //构造函数
    WebServer(
        int port, int trigMode, int timeoutMS, bool OptLinger, 
        int sqlPort, const char* sqlUser, const  char* sqlPwd, 
        const char* dbName, int connPoolNum, int threadNum,
        bool openLog, int logLevel, int logQueSize);
    //析构函数
    ~WebServer();
    //启动
    void Start();

private:
    //初始化socket
    bool InitSocket_();
    //初始化数据模式
    void InitEventMode_(int trigMode);
    //添加客户
    void AddClient_(int fd, sockaddr_in addr);
    //处理监听
    void DealListen_();
    //处理写
    void DealWrite_(HttpConn* client);
    //处理读
    void DealRead_(HttpConn* client);
    //发送错误
    void SendError_(int fd, const char*info);
    //扩展时间
    void ExtentTime_(HttpConn* client);
    //关闭连接
    void CloseConn_(HttpConn* client);
    //
    void OnRead_(HttpConn* client);
    void OnWrite_(HttpConn* client);
    void OnProcess(HttpConn* client);

    static const int MAX_FD = 65536;
    //设置fd非阻塞 
    static int SetFdNonblock(int fd);
    
    int port_;//端口
    bool openLinger_;
    int timeoutMS_;  /* 毫秒MS */
    bool isClose_;//是否关闭
    int listenFd_;//监听的文件描述符
    char* srcDir_;//源目录
    
    uint32_t listenEvent_;//监听事件
    uint32_t connEvent_;//连接事件
    /*
      智能指针
    */
    std::unique_ptr timer_;//定时器
    std::unique_ptr threadpool_;//线程池
    std::unique_ptr epoller_;//epoll
    std::unordered_map users_;//用户连接映射
};


#endif //WEBSERVER_H

#include "webserver.h"

using namespace std;

WebServer::WebServer(
            int port, int trigMode, int timeoutMS, bool OptLinger,
            int sqlPort, const char* sqlUser, const  char* sqlPwd,
            const char* dbName, int connPoolNum, int threadNum,
            bool openLog, int logLevel, int logQueSize):
            port_(port), openLinger_(OptLinger), timeoutMS_(timeoutMS), isClose_(false),
            timer_(new HeapTimer()), threadpool_(new ThreadPool(threadNum)), epoller_(new Epoller())
    {
    srcDir_ = getcwd(nullptr, 256);//获取当前工作目录的绝对路径
    assert(srcDir_);
    strncat(srcDir_, "/resources/", 16);//拼接
    HttpConn::userCount = 0;//初始化用户连接数
    HttpConn::srcDir = srcDir_;
    //数据库实例化
    SqlConnPool::Instance()->Init("localhost", sqlPort, sqlUser, sqlPwd, dbName, connPoolNum);
    
    InitEventMode_(trigMode);//初始化事件模式
    if(!InitSocket_()) { isClose_ = true;}//初始化socket失败

    if(openLog) {
        Log::Instance()->init(logLevel, "./log", ".log", logQueSize);
        if(isClose_) { LOG_ERROR("========== Server init error!=========="); }
        else {
            LOG_INFO("========== Server init ==========");
            LOG_INFO("Port:%d, OpenLinger: %s", port_, OptLinger? "true":"false");
            LOG_INFO("Listen Mode: %s, OpenConn Mode: %s",
                            (listenEvent_ & EPOLLET ? "ET": "LT"),
                            (connEvent_ & EPOLLET ? "ET": "LT"));
            LOG_INFO("LogSys level: %d", logLevel);
            LOG_INFO("srcDir: %s", HttpConn::srcDir);
            LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum);
        }
    }
}

WebServer::~WebServer() {
    close(listenFd_);//关闭监听文件描述符
    isClose_ = true;//isclose关闭
    free(srcDir_);//释放绝对路径内存
    SqlConnPool::Instance()->ClosePool();//关闭数据库
}
/*
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、
可写或者异常事件,且只触发一次。 除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。
这样,当一个线程在处理某个socket时候,其他线程是不可能有机会操作该socket的。
反过来,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,
该线程就应该立即重置这个socket上的EPOLLONESHOT事件,
以确保这个socket下一次可读时,其EPOLLIN事件能被触发,
进而让其他工作线程有机会继续处理这个socket。

*/


void WebServer::InitEventMode_(int trigMode) {
    listenEvent_ = EPOLLRDHUP;

    connEvent_ = EPOLLONESHOT | EPOLLRDHUP;
    switch (trigMode)
    {
    case 0:
        break;
    case 1:
        connEvent_ |= EPOLLET;
        break;
    case 2:
        listenEvent_ |= EPOLLET;
        break;
    case 3:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    default:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    }
    HttpConn::isET = (connEvent_ & EPOLLET);
}
/*
  开启服务器
*/
void WebServer::Start() {
    int timeMS = -1;  /* epoll wait timeout == -1 无事件将阻塞 */
    if(!isClose_) { LOG_INFO("========== Server start =========="); }
    while(!isClose_) {
        //获取超时事件
        if(timeoutMS_ > 0) {
            timeMS = timer_->GetNextTick();
        }
        //调用epoller_wait函数,阻塞timeMs,若非阻塞则不断轮询,大量消耗cpu资源
        //若阻塞,则每次关闭超时连接则需要有新的请求进来
        int eventCnt = epoller_->Wait(timeMS);
        for(int i = 0; i < eventCnt; i++) {
            /* 处理事件 */
            int fd = epoller_->GetEventFd(i);
            uint32_t events = epoller_->GetEvents(i);
            //若是监听fd
            if(fd == listenFd_) {
                DealListen_();
            }
            //若错误
            else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                assert(users_.count(fd) > 0);
                CloseConn_(&users_[fd]);
            }
            //若是已注册事件且读事件
            else if(events & EPOLLIN) {
                assert(users_.count(fd) > 0);
                DealRead_(&users_[fd]);
            }
            //若是已注册事件且写事件
            else if(events & EPOLLOUT) {
                assert(users_.count(fd) > 0);
                DealWrite_(&users_[fd]);
            } else {
                LOG_ERROR("Unexpected event");
            }
        }
    }
}
//发送错误
void WebServer::SendError_(int fd, const char*info) {
    assert(fd > 0);
    int ret = send(fd, info, strlen(info), 0);
    if(ret < 0) {
        LOG_WARN("send error to client[%d] error!", fd);
    }
    close(fd);
}
//关闭连接
void WebServer::CloseConn_(HttpConn* client) {
    assert(client);
    LOG_INFO("Client[%d] quit!", client->GetFd());
    epoller_->DelFd(client->GetFd());
    client->Close();
}

/*
  添加客户
*/
void WebServer::AddClient_(int fd, sockaddr_in addr) {
    assert(fd > 0);
    users_[fd].init(fd, addr);//将其存入用户fd库中
    //添加进时间队列中
    if(timeoutMS_ > 0) {
        timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd]));
    }
    //在事件组中添加该fd
    epoller_->AddFd(fd, EPOLLIN | connEvent_);
    //设置文件描述符非阻塞,才可以非阻塞读
    SetFdNonblock(fd);
    LOG_INFO("Client[%d] in!", users_[fd].GetFd());
}
/*
  处理监听,获取用户地址啥的
*/
void WebServer::DealListen_() {
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    do {
        int fd = accept(listenFd_, (struct sockaddr *)&addr, &len);
        if(fd <= 0) { return;}
        else if(HttpConn::userCount >= MAX_FD) {
            SendError_(fd, "Server busy!");
            LOG_WARN("Clients is full!");
            return;
        }
        AddClient_(fd, addr);
    } while(listenEvent_ & EPOLLET);
}
/*
  处理读事件,在线程池中添加读事件
  实际调用onread
*/
void WebServer::DealRead_(HttpConn* client) {
    assert(client);
    ExtentTime_(client);
    threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client));
}
/*
  处理写事件,在线程池中添加写事件
  :bind用来将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function进行保存,并延迟调用。
  实际调用onwrite
*/
void WebServer::DealWrite_(HttpConn* client) {
    assert(client);
    ExtentTime_(client);
    //交由线程池工作
    threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client));
}
/*
  拓展时间
*/
void WebServer::ExtentTime_(HttpConn* client) {
    assert(client);
    if(timeoutMS_ > 0) { timer_->adjust(client->GetFd(), timeoutMS_); }
}

void WebServer::OnRead_(HttpConn* client) {
    assert(client);
    int ret = -1;
    int readErrno = 0;
    ret = client->read(&readErrno);
    if(ret <= 0 && readErrno != EAGAIN) {
        CloseConn_(client);
        return;
    }
    //修改客户端注册fd信息
    OnProcess(client);
}

void WebServer::OnProcess(HttpConn* client) {
    //读完事件就跟内核说可以写了
    if(client->process()) {
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT);
    //写完事件就跟内核说可以读了
    } else {
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLIN);
    }
}

void WebServer::OnWrite_(HttpConn* client) {
    assert(client);
    int ret = -1;
    int writeErrno = 0;
    ret = client->write(&writeErrno);
    if(client->ToWriteBytes() == 0) {
        /* 传输完成 */
        if(client->IsKeepAlive()) {
            OnProcess(client);
            return;
        }
    }
    else if(ret < 0) {
        if(writeErrno == EAGAIN) {
            /* 继续传输 */
            epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT);
            return;
        }
    }
    CloseConn_(client);
}

/* Create listenFd */
/* 创建监听描述符   */
bool WebServer::InitSocket_() {
    int ret;
    struct sockaddr_in addr;
    if(port_ > 65535 || port_ < 1024) {
        LOG_ERROR("Port:%d error!",  port_);
        return false;
    }
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port_);
    struct linger optLinger = { 0 };
    if(openLinger_) {
        /* 优雅关闭: 直到所剩数据发送完毕或超时 */
        optLinger.l_onoff = 1;
        optLinger.l_linger = 1;
    }

    listenFd_ = socket(AF_INET, SOCK_STREAM, 0);
    if(listenFd_ < 0) {
        LOG_ERROR("Create socket error!", port_);
        return false;
    }

    ret = setsockopt(listenFd_, SOL_SOCKET, SO_LINGER, &optLinger, sizeof(optLinger));
    if(ret < 0) {
        close(listenFd_);
        LOG_ERROR("Init linger error!", port_);
        return false;
    }

    int optval = 1;
    /* 端口复用 */
    /* 只有最后一个套接字会正常接收数据。 */
    ret = setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int));
    if(ret == -1) {
        LOG_ERROR("set socket setsockopt error !");
        close(listenFd_);
        return false;
    }

    ret = bind(listenFd_, (struct sockaddr *)&addr, sizeof(addr));
    if(ret < 0) {
        LOG_ERROR("Bind Port:%d error!", port_);
        close(listenFd_);
        return false;
    }

    ret = listen(listenFd_, 6);
    if(ret < 0) {
        LOG_ERROR("Listen port:%d error!", port_);
        close(listenFd_);
        return false;
    }
    ret = epoller_->AddFd(listenFd_,  listenEvent_ | EPOLLIN);
    if(ret == 0) {
        LOG_ERROR("Add listen error!");
        close(listenFd_);
        return false;
    }
    SetFdNonblock(listenFd_);
    LOG_INFO("Server port:%d", port_);
    return true;
}
//设置非阻塞
int WebServer::SetFdNonblock(int fd) {
    assert(fd > 0);
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK);
}


webbench -c 1000 -t http://xxx:xx/index.html

总结

好好学习,前路漫漫。

你可能感兴趣的:(c++,服务器)