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

目录

一、创建好运行环境

二、编译过程

三、makefile 文件

四、gdb调试过程

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

五、静态库和动态库

六、一些概念

七、TCP通信模型

八、reactor模式

九、具体代码分析

动态空间分配类

线程池类

HTTP请求类

http_response

httpconn

epoll

计时器

Webserver

总结


简单记录一下学习过程

一、创建好运行环境

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

创建免密登录的步骤

在windows操作台上输入

ssh-keygen -t rsa

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

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

vim authorized_keys

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

二、编译过程

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

编译就是生成汇编代码

汇编就是生成机器代码

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

三、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.oyul.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通信模型

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

八、reactor模式

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

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

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

要求主线程( 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 <cstring>   //perror
#include <iostream>
#include <unistd.h>  // write
#include <sys/uio.h> //readv
#include <vector> //readv
#include <atomic>
#include <assert.h>
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<char> buffer_;//内存空间std::atomic<std::size_t> readPos_;//可读相对位置std::atomic<std::size_t> 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<const char*>(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<size_t>(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 <mutex>
#include <condition_variable>
#include <queue>
#include <thread>
#include <functional>
class ThreadPool {
public:/*线程池类初始化,使用初值赋予可以减少值得拷贝次数*///explicit指定构造函数或转换函数 (C++11 起) 或推导指南 (C++17 起) 是显式的,也就是说,它不能用于隐式转换和复制初始化。explicit ThreadPool(size_t threadCount = 8): pool_(std::make_shared<Pool>()) {assert(threadCount > 0);//创建threadCount个线程for(size_t i = 0; i < threadCount; i++) {std::thread([pool = pool_] {//匿名函数,输入变量poolstd::unique_lock<std::mutex> 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<bool>(pool_)) {{//lock_grard可以保证锁一定解除并且删除std::lock_guard<std::mutex> locker(pool_->mtx);pool_->isClosed = true;}pool_->cond.notify_all();}}/*模板类*/template<class F>void AddTask(F&& task) {{   /*lock_guard:没有提供加锁和解锁的接口。通过构造函数和析构函数控制锁的作用范围,创造对象的时候加锁,离开作用域的时候解锁;*/std::lock_guard<std::mutex> locker(pool_->mtx);/*std::forward通常是用于完美转发的,它会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。*/pool_->tasks.emplace(std::forward<F>(task));}pool_->cond.notify_one();//唤醒一个条件变量}private:/*池类私有类,把继承的类定义为私有的类*/struct Pool {std::mutex mtx;//互斥锁std::condition_variable cond;//条件变量bool isClosed;//判断是否关闭/*std::function是C++11标准库中提供的一种可调用对象的通用类型,它可以存储任意可调用对象,如函数指针,函数对象,成员函数指针和lambda表达式。*/std::queue<std::function<void()>> tasks;//存储需要执行的工作};std::shared_ptr<Pool> pool_;//智能指针,创建该类和自动销毁此类,可以被多个对象调用
};#endif //THREADPOOL_H

HTTP请求类

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


#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H#include <unordered_map>
#include <unordered_set>
#include <string>
#include <regex>
#include <errno.h>
#include <mysql/mysql.h>  //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;//得到poststd::string GetPost(const char* key) const;//得到postbool 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<std::string, std::string> header_;//头std::unordered_map<std::string, std::string> post_;//请求static const std::unordered_set<std::string> DEFAULT_HTML;//默认HTML集合static const std::unordered_map<std::string, int> DEFAULT_HTML_TAG;//默认htmltagstatic int ConverHex(char ch);//转换为16进制
};#endif //HTTP_REQUEST_H

#include "httprequest.h"
using namespace std;//初始化无序集合  DEFAULT_HTML并赋值
const unordered_set<string> HttpRequest::DEFAULT_HTML{"/index", "/register", "/login","/welcome", "/video", "/picture", };
//初始化无序映射  DEFAULT_HTML_TAG并赋值
const unordered_map<string, int> 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 <unordered_map>
#include <fcntl.h>       // open
#include <unistd.h>      // close
#include <sys/stat.h>    // stat
#include <sys/mman.h>    // 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<std::string, std::string> SUFFIX_TYPE;//后缀类型集static const std::unordered_map<int, std::string> CODE_STATUS;//编码状态集static const std::unordered_map<int, std::string> CODE_PATH;//编码路径集
};#endif //HTTP_RESPONSE_H
#include "httpresponse.h"using namespace std;
//后缀类型键值对
const unordered_map<string, string> 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<int, string> HttpResponse::CODE_STATUS = {{ 200, "OK" },{ 400, "Bad Request" },{ 403, "Forbidden" },{ 404, "Not Found" },
};
//错误编码路径,键值对
const unordered_map<int, string> 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参数有很多种取值,这里只讲两种,其它取值可查看mmapMAP_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_sizeclose(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 += "<html><title>Error</title>";body += "<body bgcolor=\"ffffff\">";if(CODE_STATUS.count(code_) == 1) {status = CODE_STATUS.find(code_)->second;} else {status = "Bad Request";}body += to_string(code_) + " : " + status  + "\n";body += "<p>" + message + "</p>";body += "<hr><em>TinyWebServer</em></body></html>";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 <sys/types.h>
#include <sys/uio.h>     // readv/writev
#include <arpa/inet.h>   // sockaddr_in
#include <stdlib.h>      // atoi()
#include <errno.h>      #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;//获取文件fdint GetPort() const;//获取文件端口const char* GetIP() const;//获取ipsockaddr_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<int> 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 <sys/types.h>
#include <sys/uio.h>     // readv/writev
#include <arpa/inet.h>   // sockaddr_in
#include <stdlib.h>      // atoi()
#include <errno.h>      #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;//获取文件fdint GetPort() const;//获取文件端口const char* GetIP() const;//获取ipsockaddr_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<int> 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 <sys/epoll.h> //epoll_ctl()
#include <fcntl.h>  // fcntl()
#include <unistd.h> // close()
#include <assert.h> // close()
#include <vector>
#include <errno.h>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<struct epoll_event> 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<int>(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 <queue>
#include <unordered_map>
#include <time.h>
#include <algorithm>
#include <arpa/inet.h>
#include <functional>
#include <assert.h>
#include <chrono>
#include "../log/log.h"typedef std::function<void()> 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<TimerNode> heap_;//小根堆std::unordered_map<int, size_t> 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<MS>(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<MS>(heap_.front().expires - Clock::now()).count();if(res < 0) { res = 0; }}return res;
}

Webserver

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

#ifndef WEBSERVER_H
#define WEBSERVER_H#include <unordered_map>
#include <fcntl.h>       // fcntl()
#include <unistd.h>      // close()
#include <assert.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#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://初始化socketbool 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<HeapTimer> timer_;//定时器std::unique_ptr<ThreadPool> threadpool_;//线程池std::unique_ptr<Epoller> epoller_;//epollstd::unordered_map<int, HttpConn> 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);//若是监听fdif(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]));}//在事件组中添加该fdepoller_->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++实现的高性能WEB服务器相关推荐

  1. nginx高性能WEB服务器系列之九--nginx运维故障日常解决方案

    nginx系列友情链接: nginx高性能WEB服务器系列之一简介及安装 https://www.cnblogs.com/maxtgood/p/9597596.html nginx高性能WEB服务器系 ...

  2. 分享关于搭建高性能WEB服务器的一篇文章

    这篇文章主要介绍了Centos5.4+Nginx-0.8.50+UWSGI-0.9.6.2+Django-1.2.3搭建高性能WEB服务器的相关资料,需要的朋友可以参考下(http://m.0813s ...

  3. LEMP构建高性能WEB服务器(第三版)

    LEMP 自动化编译脚本下载:http://docs.linuxtone.org/autoinstall/ (定期更新,欢迎多测试,找bug) 介绍参考:http://bbs.linuxtone.or ...

  4. nginx高性能WEB服务器系列之七--nginx反向代理

    nginx系列友情链接: nginx高性能WEB服务器系列之一简介及安装 https://www.cnblogs.com/maxtgood/p/9597596.html nginx高性能WEB服务器系 ...

  5. Nginx高性能Web服务器实战教程PDF

    网站 更多书籍点击进入>> CiCi岛 下载 电子版仅供预览及学习交流使用,下载后请24小时内删除,支持正版,喜欢的请购买正版书籍 电子书下载(皮皮云盘-点击"普通下载" ...

  6. 服务器后端开发系列——《实战Nginx高性能Web服务器》

    1.高性能Web服务器Nginx的配置与部署研究(1)Nginx简介及入门示例 内容:概述Nginx的背景知识和简单的入门实例. 2.高性能Web服务器Nginx的配置与部署研究(2)Nginx入门级 ...

  7. Nginx高性能Web服务器详解

    Nginx高性能Web服务器详解 1. 什么是Nginx 1.1 优点 1.2 缺点 2. Nginx负载均衡策略 2.1 轮询策略 2.2 加权轮询策略 2.3 IP hash策略 3. 常用指令 ...

  8. Warp : Haskell 的高性能 Web 服务器(译文)

    Warp : Haskell 的高性能 Web 服务器(译文) 按 GHC 7.8 马上就要发布了.一个很大的改进就是加入了本文所说的并行 IO 管理器.从此之后 Haskell 在高性能服务器领域将 ...

  9. [nginx] LEMP构建高性能WEB服务器(第三版)

    LEMP 自动化编译脚本下载:http://docs.linuxtone.org/autoinstall/ (定期更新,欢迎多测试,找bug) 介绍参考:http://bbs.linuxtone.or ...

  10. 基于linuxunix高性能web服务器架构思路分析

    随着21世纪互联网的快速发展以及web2.0的诞生,最初web服务器已经不能满足我们的需求.而现在我们要考虑的不再仅仅是web服务器以及数据库服务器这么简单,我们所需要考虑的就是设计出一套高性能web ...

最新文章

  1. 10a大电流稳压芯片_稳压二极管你见过,但是它的这些参数你知道吗
  2. 机器学习(1)特征选择与特征抽取
  3. angular4更改表单中显示的值_Angular 4 动态表单教程 Reactive Forms教程
  4. ROS学习之节点间话题通信的
  5. java内部格式_详解java内部类的访问格式和规则
  6. python客户端和服务端实验_结合服务器和客户端python
  7. SocketAsyncEventArgs
  8. c语言如何删除数组中的某一个元素_数据结构之线性表高效删除重复元素
  9. python3 有序字典
  10. 【翻译】MongoDB指南/CRUD操作(二)
  11. The “FixedStepDiscrete“ solver cannot be used to simulate block diagram ‘foc_ruizi‘ because it conta
  12. android 5.0 屏幕录制,Android 5.0+ 视频录制 ScreenCapture
  13. Mybatis-plus 代码生成器(新)工具类
  14. ubuntu22.04 耳机没声音 [已解决]
  15. 中餐菜单分类名称创意_餐厅菜单的种类分类
  16. Spark的调度流程(任务调度+资源调度)
  17. 视频怎么剪辑成短视频?如何制作原创视频素材作品
  18. libvirt零知识学习6 —— libvirt源码编译安装(4)
  19. php敏感词过滤的一个方法
  20. android 版本更新忽略,安卓微信7.0.14 正式版更新,几个被你忽略的微信小技巧

热门文章

  1. python爬取推特的详细教程_如何利用 Twitter 开放者平台爬取 Twitter 数据?
  2. RabbitMQ基础篇
  3. 计算机关闭硬盘休眠是什么意思,关闭硬盘盒自动休眠
  4. 太强了,全面解析缓存应用经典问题
  5. 获取猫眼电影所有城市信息
  6. 公有云的这五大定律 看看谁能跑得更远?
  7. error creating overlay mount to /var/lib/docker/overlay2
  8. 业精于勤荒于嬉,行成于思毁于随
  9. 友盟第三方登录 无法切换账号 退出 取消授权
  10. 魔兽世界9.0主播最多的服务器,斗鱼主播服务器分布揭秘!魔兽世界怀旧服精彩不间断...