从零开始实现 C++ TinyWebServer 项目总览
文章目录
- 引言
- Web Server 概念
- 如何实现高性能 WebServer?
- 基础网络通信
- I/O 多路复用技术
- 并发处理
- 事件处理模式
- 其他优化策略(未实现)
- 主要功能模块
- Buffer
- Log
- SqlConnectPool
- HttpRequest
- HttpResponse
- HttpConnect
- HeapTimer
- WebServer
引言
TinyWebServer
是一个使用现代 C++ 编写的轻量级、高性能且具备高并发处理能力的 Web 服务器。该服务器通过 webbench
压力测试,能够达到上万的 QPS(每秒查询率)。其实现结合了多种高效技术,如 IO 复用、线程池、状态机、小根堆定时器等,同时提供了异步日志系统和数据库连接池,还支持用户注册登录功能。
Web Server 概念
Web Server 本质上是一个服务器软件或运行该软件的硬件。它的核心任务是依据 HTTP 协议与客户端(如浏览器)进行交互,接收客户端发送的 HTTP 请求,处理并存储相关数据,然后生成 HTTP 响应,将客户端请求的内容(如文件、网页)或错误信息返回给客户端。用户在浏览器中输入 “域名” 或 “IP 地址:端口号”,浏览器会先将域名解析为 IP 地址,随后通过 TCP 协议的三次握手与目标 Web 服务器建立连接,再利用 HTTP 协议生成请求报文,并借助 TCP、IP 等协议将请求发送到服务器。
如何实现高性能 WebServer?
基础网络通信
- Socket 监听:Web 服务器利用
socket
函数创建监听套接字,通过bind
函数将其绑定到指定的 IP 地址和端口,再使用listen
函数开始监听来自客户端的连接请求。当有客户端尝试通过connect
函数连接到该端口时,这些连接会被放入监听队列等待处理。 - 连接处理:服务器需要在合适的时机调用
accept
函数从监听队列中取出连接,为每个连接分配一个新的套接字用于与客户端进行通信。
I/O 多路复用技术
- I/O 复用机制:在 Linux 环境中,常见的 I/O 复用技术有
select
,poll
和epoll
。
epoll
由于其高效性,尤其适合处理大量并发连接的场景。
select
:它可以监听多个文件描述符的可读、可写和异常状态,但存在文件描述符数量限制,且每次调用select
都需要将文件描述符集合从用户空间复制到内核空间,效率较低。poll
:与select
类似,但去除了文件描述符数量的限制,不过同样存在数据复制的开销。epoll
:采用事件驱动机制,通过epoll_ctl
函数注册文件描述符和对应的事件,使用epoll_wait
函数等待事件发生。它只返回就绪的文件描述符,避免了对所有文件描述符的遍历,并且内核与用户空间使用mmap
共享内存,减少了数据复制的开销。
并发处理
- 线程池的使用:由于 I/O 复用本身是阻塞的,当有多个文件描述符同时就绪时,为了提高处理效率,需要引入线程池实现并发处理。线程池预先创建一定数量的线程,当有就绪的文件描述符时,将对应的处理任务分配给线程池中的空闲线程。
- 线程池的优势:避免了频繁创建和销毁线程带来的开销,提高了系统资源的利用率。同时,通过合理设置线程池的大小,可以根据服务器的硬件资源和业务需求进行优化。
事件处理模式
- Reactor 模式
- 工作原理:主线程(I/O 处理单元)负责监听文件描述符上的事件,当有事件发生时,将事件放入请求队列,通知工作线程(逻辑单元)进行处理。主线程在这个过程中只负责事件的监听和分发,不进行具体的数据读写操作。
- 优点:实现相对简单,不需要复杂的异步 I/O 接口,适合大多数应用场景。
- 缺点:工作线程需要进行数据的读写操作,可能会导致一定的延迟。
- Proactor 模式
- 工作原理:主线程和内核负责所有的 I/O 操作,包括数据的读写。当 I/O 操作完成后,通知工作线程进行逻辑处理。
- 优点:理论上可以实现更高的性能,因为工作线程只需要处理逻辑,不需要进行 I/O 操作。
- 缺点:在 Linux 环境下,没有原生的异步 AIO 接口,需要使用同步 I/O 模拟实现,增加了实现的复杂度,且实际效果可能并不理想。
其他优化策略(未实现)
- 负载均衡:使用 Nginx 等负载均衡器将客户端请求均匀地分配到多个 Web 服务器上,提高系统的整体处理能力和可用性。
- 缓存技术:采用 HTTP 缓存和 CDN 缓存,减少对服务器的请求,提高响应速度。
- 数据库优化:使用读写分离、分库分表、索引优化等技术,提高数据库的性能和并发处理能力。
主要功能模块
项目源码
WebServer :服务器逻辑框架: epoller监听+线程池读写
|
|
Epoller Timer :epoll操作封装, 定时器给连接计时
| |
----------
|
HttpConnection :把监听连接返回的文件描述符封装成一个连接实例, 对readv, write网络数据传输进行封装, 管理连接
| |
HttpRequest HttpResponse :请求操作封装,响应操作封装,业务逻辑
| |
--------------
|
Buffer :读写缓冲区
ThreadPool : 线程池,负责读写操作(上图上两层属于主线程,下三层属于线程池) Log : 日志类
Buffer
从零开始实现 C++ TinyWebServer 缓冲区 Buffer类详解
Buffer
以std::vector<char>
为存储实体,通过std::atomic<std::size_t>
类型的read_index_
和write_index_
分别标记读写位置,利用readv
结合栈上空间实现分散读,提供读写接口、容量管理和数据操作方法,确保数据的高效读写与存储。- 使用
Buffer
可以避免开巨大Buffer
造成的内存浪费,减少反复调用read()
的系统开销,且通过原子类型保证多线程环境下安全高性能执行,同时可高效处理数据的读写、存储和管理。
Log
从零开始实现 C++ TinyWebServer 阻塞队列 BlockQueue类详解
从零开始实现 C++ TinyWebServer 异步日志系统 Log类详解
Log
采用单例模式,支持同步和异步两种写入方式,异步时利用阻塞队列和写线程实现,同步则直接写入;能根据日志级别添加标题,按天和行数对日志文件进行分割,通过Init
方法初始化,Write
方法写入日志。- 使用该日志系统可灵活选择同步或异步方式记录程序运行情况,能按级别分类输出,自动按天和行数分割日志文件,便于调试和监控服务器状态,且异步模式可减少 I/O 操作对主线程的阻塞,提高并发处理能力。
SqlConnectPool
从零开始实现 C++ TinyWebServer 数据库连接池 SqlConnectPool详解
SqlConnPool
采用单例模式,在初始化时创建指定数量的 MySQL 连接存入队列,使用信号量管理资源数量,利用互斥锁保证线程安全,提供获取和释放连接的接口,同时通过SqlConnRAII
类基于 RAII 机制管理连接的生命周期。- 使用
SqlConnPool
可以避免服务器频繁创建和断开数据库连接带来的性能开销与安全隐患,在程序初始化时集中创建并管理多个数据库连接,提高数据库读写速度,并且通过 RAII 机制确保资源的正确释放。
HttpRequest
从零开始实现 C++ TinyWebServer 处理请求 HttpRequest类详解
HttpRequest
类通过状态机机制,利用正则表达式解析 HTTP 请求报文,按请求行、请求头、请求体顺序解析,支持 GET 和 POST 请求,POST 请求时解析表单数据并验证用户登录注册信息,同时可统一处理请求路径,具备初始化和判断长连接的功能。
HttpResponse
从零开始实现 C++ TinyWebServer 构建响应 HttpResponse类详解
HttpResponse
类用于生成 HTTP 响应报文,在初始化时接收源目录、请求路径、是否保持连接和状态码等信息,根据文件状态确定响应状态码,通过添加状态行、消息报头和响应正文构建响应报文,利用文件映射提高访问速度,支持错误处理和资源释放。
HttpConnect
从零开始实现 C++ TinyWebServer 网络连接 HttpConnect类详解
HttpConn
类负责处理 HTTP 连接,通过init
方法初始化连接信息,利用read
方法从套接字读取数据存入读缓冲区,调用HttpRequest
解析请求,根据解析结果用HttpResponse
生成响应报文存入写缓冲区,再通过write
方法将响应数据发送给客户端,同时提供关闭连接、获取连接信息等功能,支持 ET 模式读写。
HeapTimer
从零开始实现 C++ TinyWebServer小根堆定时器 HeapTimer详解
HeapTimer
类基于小根堆数据结构实现时间堆,将定时器封装为TimerNode
结构体,通过std::vector
存储节点,用std::unordered_map
记录节点索引,提供添加、调整、删除定时器及清除超时节点等功能,以管理定时事件。- 使用
HeapTimer
可以高效地管理大量定时事件,借助小根堆特性快速定位并处理最早超时的事件,减少不必要的轮询,降低服务器资源消耗,提高系统性能和响应速度。
WebServer
从零开始实现 C++ TinyWebServer 线程池 ThreadPool详解
从零开始实现 C++ TinyWebServer IO多路复用 Epoller详解
从零开始实现 C++ TinyWebServer 网络服务器 WebServer详解
-
WebServer
在构造时完成线程池、定时器、epoller 等初始化,通过InitSocket
建立监听套接字并加入 epoller,Start
方法利用epoll_wait
监听事件,对新连接调用DealListen
处理,对读写事件分别调用DealRead
和DealWrite
并交由线程池处理,同时使用定时器管理超时连接。 -
使用
WebServer
可借助 epoll 实现高效的 I/O 多路复用,通过线程池处理并发请求,利用定时器管理非活动连接,结合数据库连接池减少开销,且支持日志记录,能快速稳定地处理大量 HTTP 请求。