# 基于C++11多线程主从Reactor模式的Web高性能服务器
## 简介
* 本项目使用C++11标准编写了一个遵循One Loop Per Thread思想的Web高性能服务器。
* 并发模型使用主从Reactor模式+线程池,Socket使用非阻塞IO,IO多路复用使用Epoll ET边缘触发工作模式。
* 主线程也就是主Reactor(MainEventLoop)只负责accept客户端的请求,接收请求后将新连接以Round Robin轮转的方式平均的分发给线程池里的每个线程,
每个线程里都有一个子Reactor(SubEventLoop),子Reactor(SubEventLoop)负责与客户端进行交互,这里的线程是IO线程,没有创建工作线程,所以IO线程里兼顾计算。
* 使用状态机解析HTTP请求,支持HTTP GET、POST、HEAD请求方法,支持HTTP长连接与短连接。
* 使用小根堆做定时器,惰性删除超时的任务,即客户端没有向服务器发送请求的时间已经超过了我们给定的超时时间,当再次访问它的时候才会去关闭这个连接。
* 实现了双缓冲区的异步日志系统,记录服务器运行状态。
* 使用智能指针等RAII机制,减少内存泄漏的可能。
* 使用eventfd实现了线程的异步唤醒,每个EventLoop将eventfd注册到epoll内核表中,当新事件被accept后分发到某个SubEventLoop,
但此时没有就绪事件Loop阻塞在epoll_wait中,主线程向eventfd中写入数据,此时Loop的epoll_wait因有可读事件就绪而被唤醒,去添加新连接。
## 并发模型
![并发模型](https://github.com/ashen7/WebServer/blob/master/resource/WebServer%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B.png)
## 环境
* OS: Ubuntu 18.04
* Complier: g++ 7.5.0
* Debugger: gdb 8.1.1
* CMake: 3.15.4
* Makefile: 4.1
## 构建
* 使用CMake来build
mkdir build
cd build
cmake .. && make -j8 && make install
cd ..
* 使用Makefile来build
make -j8 && make install
* 直接运行脚本
./build.sh
## 运行
./web_server [-p port] [-t thread_numbers] [-f log_file_name] [-o open_log] [-s log_to_stderr] [-c color_log_to_stderr] [-l min_log_level]
## 压力测试
* 本项目对开源压测工具WebBench进行了代码的修改,将其作为了子模块,用git submodule init && git submodule update来添加。修改后的源码:[WebBench](https://github.com/ashen7/WebBench.git)
* 修复了WebBench connect()失败时sockfd泄漏的bug,以及接收响应报文时读完了依然read导致阻塞的bug(因为是BIO,读完了再读就会阻塞了)。
* 添加支持HTTP1.1长连接 Connection: keep-alive。
## 压测结果
* 压测之前做的工作:
设置了几个关于操作系统对单个进程资源的一些限制, 用limit命令查看,
首先为了方便调试程序(程序段错误core dump), 将codedumpsize设置为unlimited,
然后将单个进程可以打开的文件描述符descriptors设置成了100w,
最后设置了能够使用的端口号,设置的从10000开始到65536都可以使用,也就是5w多个端口。
* 在程序层面,因为压测的是echo server, 响应报文除了状态行和响应头,响应体部分是简单的hello world,
所有程序中关闭了套接字的TCP Nagle算法, 避免响应时间过久,每次数据直接发,而不用等到一定量再一起发。
* 压力测试开启1000个进程,访问服务器60s,过程是客户端发出请求,然后服务器读取并解析,返回响应报文,客户端读取。
长连接因为不必频繁的创建新套接字去请求,然后发送数据读取数据,关闭套接字等操作,所以比短连接QPS高很多。
HTTP长连接 QPS: 26万
![长连接](https://github.com/ashen7/WebServer/blob/master/resource/WebServer%E9%95%BF%E8%BF%9E%E6%8E%A5QPS.png)
## 代码结构
* include/server和src/server: WebServer主接口
web_server.h/web_server.cpp: [server::WebServer类],单例模式(线程安全的懒汉式),由主线程调用。
Initialize(绑定服务器,设置监听套接字为NIO,给监听套接字的Channel绑定回调函数, 创建EventLoop线程池)。
Start(线程池创建线程,每个线程就是一个SubEventLoop,线程函数就是Loop函数(epoll_wait等待处理就绪事件),
给监听套接字绑定可读回调事件(while true的accept连接,对新套接字关闭TCP Nagle算法(小数据不用等着一起发,而是直接发),
将此新连接以轮转的方式分发给线程池中其中一个SubEventLoop,由于此时SubEventLoop正在epoll_wait中阻塞,
这里主线程通过QueueInLoop(学习muduo做法,内部通过eventfd将其异步唤醒),然后SubEventLoop将此新连接绑定回调函数(处理http连接))。
* include/event和src/event: 主从Reactor模式+线程池
channel.h/channel.cpp: [event::Channel类],
给不同的文件描述符绑定不同的可读/可写/更新/错误回调函数(主要是监听套接字和连接套接字)。
poller.h/poller.cpp: [event::Poller类],
封装了epoll的操作(后续会支持poll和select,在所有连接都活跃的情况下,使用epoll并没有poll或select性能好)。
event_loop.h/event_loop.cpp: [event::EventLoop类],
每个EventLoop都有一个Poller(epoll内核事件表)。
Loop(epoll_wait等待就绪事件的到来,有就绪事件后处理就绪事件, 处理PendingFunctions(学习muduo), 处理超时事件)。
RunInLoop(如果不是跨线程调用就直接执行,否则放入等待执行函数区)。
QueueInLoop(将任务放入等待执行函数区,然后用eventfd异步唤醒SubEventLoop的epoll_wait, Loop里就会处理这个等待执行函数区)。
PerformPendingFunctions(执行等待执行函数区的函数,这里的func是将新连接注册到这个SubEventLoop的epoll内核事件表的)。
event_loop_thread.h/event_loop_thread.cpp, event_loop_thread_pool.h/event_loop_thread_pool.cpp: [event::EventLoopThreadPool类],
event_loop的线程池,每个线程里一个SubEventLoop,SubEventLoop调用Loop函数
* include/http_connection.h和src/http_connection.cpp: HTTP连接的处理
http_connection.h/http_connection.cpp: [http::HttpConnection类],
读取客户端发来的请求数据存到read_buffer_(read)。
解析请求行,解析请求头,解析请求体(POST方法), 构建响应报文,将响应报文写入write_buffer_(将请求的文件内容通过mmap映射到一块共享内存中,用户态和内核态内存零拷贝,加快访问速度)。
将write_buffer_中的响应报文数据发送给客户端(write)。
http_type.h/http_type.cpp: [http::HttpType类],
定义一些http的enum。
* include/timer和src/timer: 用小根堆管理定时器
timer.h/timer.cpp: [timer::Timer类],
通过gettimeofday函数得到当前时间以毫秒计算,到期时间=当前秒%10000然后*1000(换算成毫秒),加上当前微秒/1000(也是换算成毫秒), 加上我们设定的超时时间。
每次处理完请求后会重新计时(这里是将到期时间用当前时间再重算一遍)。
timer_heap.h/timer_heap.cpp: [timer::TimerHeap类],
使用C++ STL的优先级队列,底层是小根堆,来管理定时器,小根堆的堆头是会最先到期的定时器,所以每次只有pop头,O(1)的删除,O(logN)的插入。
每次Loop最后会去检测有无连接超时,超时则删除,这里是惰性删除(检查之前可能已经超时了但没有主动去删)。
* include/log和src/log: 双缓冲区异步日志系统
log_stream.h/log_stream.cpp: [Buffer类], [log::LogStream类],
设计了一个固定大小的缓冲区Buffer类(模板类,模板参数是buffer size)。
LogStream则是重载输出流运算符<<, <<后面接的数据都写入Buffer中,重载了长短int, float, double, char, const char*, string等数据类型
没有合适的资源?快使用搜索试试~ 我知道了~
温馨提示
【项目资源】: 包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。 包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。
资源推荐
资源详情
资源评论
收起资源包目录
基于C++11多线程主从Reactor模式的Web高性能服务器.zip (49个子文件)
资料总结
include
thread
block_queue.hpp 3KB
thread_pool.h 1KB
thread.h 2KB
worker_thread_pool.h 840B
utility
noncopyable.h 565B
count_down_latch.h 938B
socket_utils.h 1KB
timer
timer_heap.h 1KB
timer.h 931B
http
http_type.h 2KB
http_connection.h 4KB
event
channel.h 3KB
event_loop_thread.h 738B
poller.h 2KB
event_loop.h 3KB
event_loop_thread_pool.h 710B
locker
mutex_lock.h 2KB
log
async_logging.h 3KB
logging.h 2KB
log_stream.h 4KB
CMakeLists.txt 3KB
run_server.sh 515B
Makefile 2KB
src
thread
thread_pool.cpp 4KB
thread.cpp 2KB
worker_thread_pool.cpp 1KB
utility
socket_utils.cpp 9KB
timer
timer.cpp 2KB
timer_heap.cpp 1KB
http
http_connection.cpp 22KB
http_type.cpp 6KB
event
poller.cpp 5KB
event_loop_thread_pool.cpp 2KB
event_loop.cpp 6KB
channel.cpp 2KB
event_loop_thread.cpp 1KB
log
log_stream.cpp 3KB
async_logging.cpp 4KB
logging.cpp 4KB
LICENSE 11KB
resource
index.html 629B
WebServer运行时状态.png 1.97MB
WebServer长连接QPS.png 503KB
WebServer并发模型.png 136KB
WebServer短连接QPS.png 304KB
main.cpp 3KB
.gitmodules 86B
README.md 10KB
build.sh 432B
共 49 条
- 1
资源评论
妄北y
- 粉丝: 1w+
- 资源: 1万+
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功