Linux 下的轻量级 Web 服务器,使用 C++11 开发,架构参考 Github 高星项目 TinyWebServer。参考链接:Tinyweb
。
使用 webbench 压测能够实现上万并发连接数据交换。
简单的分为三层:数据层、服务层、连接层。其中数据层负责与MySQL数据库建立连接,进行交互;服务层负责http报文的解析和构建;连接层负责socket监听服务以及连接操作,对于https服务,提供ctx会话环境的构建和ssl连接。
.
├── http //提供http报文解析和生成服务
│ ├── httpconn.cpp
│ ├── httpconn.h //集成httpRequestParser和httpResponse,并负责提供静态资源托管接口、get和post路由设置函数
│ ├── httpRequestParser.cpp
│ ├── httpRequestParser.h //http报文解析
│ ├── httpResponse.cpp
│ ├── httpResponse.h //http报文构建
│ └── README.md
├── log //异步日志,来自[Ring Log]([email protected]:LeechanX/Ring-Log.git),进行了一点修改
│ ├── Makefile
│ ├── README.md
│ ├── rlog.cc
│ ├── rlog.h
│ └── rlog.o
├── make.sh
├── mysql //数据库连接池,以及提供数据库操作函数
│ ├── connect_pool.cpp
│ └── connect_pool.h //单例模式,使用GetConnection获取数据库连接指针,ReleaseConnection放回数据库连接池。提供ConnectRAII对象管理数据库连接,使用RAII保证数据连接会安全放回连接池。使用init函数初始化数据库连接池对象,并且保证init函数只会调用一次。
├── server.cpp
├── server.h //连接层,负责监听socket连接,将连接放入线程池以及主动断开长时间未操作的连接,webserver类用来定义一个完整的web服务器对象。
├── threadPool //线程池类,为连接层提供线程池,使用模板实现,便于扩展(可以提供除http外的其他服务)
│ ├── README.md
│ └── threadPool.h
└── timer //定时器类,为连接层提供定时断连服务。
├── README.md
├── timer_fd.cpp
└── timer_fd.h
threadPool
使用模板便于扩展,之后可以添加除了http之外的其他服务。
线程同步采用了c++11提供的
、
,线程库采用了c++11的thread
。
std::enable_shared_from_this>
使用智能指针管理threadPool对象,当对象析构时,停止线程循环,执行线程回收工作。
shared_ptr
init()函数负责初始化线程池对象,构建对应数目的线程,并关联线程函数work(),传入线程池模板类对应的this的智能指针(采用shared_from_this
),在work
中就能调用threadPool
对象的工作队列workList
进行处理。
try
对于thread线程创建增加异常捕获,若线程创建失败,则不需要把线程添加进m_threadList
,整个程序也不会因此崩溃,只是实际上创建的线程会少于预期。
//增加异常捕获,如果当前线程创建失败,则不需要添加进m_threadList
try{
//把当前对象的智能指针传入work函数,因为work函数必须是静态函数,而静态函数没有对象的this指针,所以使用这种折中的方法
std::shared_ptr<std::thread> t = std::make_shared<std::thread>(work, threadPool<T>::shared_from_this());
temp.swap(t);
}
catch(const std::system_error &e){
//报告错误
LOG_FATAL("Create thread eroror : %s", e.what());
continue;
}
....
//work函数
template<typename T>
void threadPool<T>::work(std::shared_ptr<threadPool<T>> request){
//使用threadPool对象的run方法,也就是业务循环段
request->run();
}
http
、http_request
、http_response
类http类整合了:http报文读取:read_once
、http报文发送:write_once()
、http请求报文解析和响应报文构建:process()
static std::map staticSource
负责静态资源的托管,static std::map registerGet;
static std::map registerPost;
其中http报文解析使用了http_request类专门负责报文解析模块,http响应报文构建专门使用了http_response类负责响应报文构建。
init(int epollfd, int sockfd, SSL* ssl)
初始化,传入epoll、socket文件描述符,以及SSL*,当SSL*不为空时,认为执行https服务,空时执行http服务。http::read_once
从socket
中读取http请求报文,并对读取情况进行判定,read_once返回结果是READ_STAT对象
,enum class READ_STAT{OK, BAD, CLOSE};
分别代表读取成功、失败以及对方关闭连接。process()函数
,process()函数首先对报文进行解析,解析函数返回HTTP_CODE类型对象,根据不同的解析结果生成不同的响应报文,除了“报文完整”,其他都是错误信息,对于“报文完整”的情况,会跳跳转get_request()
函数,分别执行GET请求和POST的事务,并构建响应报文。//解析报文
HTTP_CODE code = this->request_parase.parser(this->m_message);
....
//HTTP报文解析结果:报文不完整,报文完整,报文错误, 内部错误(兜底)
enum class HTTP_CODE{OPEN_REQUEST = 0, GET_REQUEST, BAD_REQUEST, INTERNAL_ERROR};
timefdList
timefdList
类维护一个定时器列表std::list
,该m_list维护一个智能指针,指针指向vector,vector是一个长度为2的数组,分别存储socket文件描述符
和timerfd_create创建的定时器对象文件描述符
。
标记
,等这次epoll_wait返回的事件都处理完毕,调用timefdList的tick方法处理所有超时连接。webserver类
websever负责整合所有功能,主要其监听连接,并发模型使用模拟proactor
执行首先使用webListen设置监听端口,如果开启https,则需要使用openhttps传入响应文件,addStaticSource、addGet、addPost添加静态资源和GET、POST路由。
当执行run
之后,服务器主循环开始,主循环epoll_wait等待epoll注册事件发生,对于接收到的事件分为4种类型:
alarm_event
标记置true,等到此次epoll事件处理完毕,再调用定时器列表的tick方法统一关闭超时的socket连接。