Skip to content

norinyan/TinyReactor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TinyReactor

TinyReactor 是一个基于 Reactor 模式 实现的轻量级 C++ Web 服务器项目。

目标不是简单复刻,而是以工程化方式从零手写,在实践中学习 Linux 系统编程、网络编程、并发编程等核心知识。

技术栈

  • 语言:C++17
  • 构建:CMake
  • 开发环境:Docker(Ubuntu 22.04)
  • 编译器:g++ 11.4.0
  • 数据库:MySQL 8.0

开发环境

本项目全程在 Docker 容器中开发,保证跨平台环境一致。

# 启动容器
docker compose up -d

# 进入开发容器
docker exec -it tinyreactor_dev bash

# 编译
cd /workspace/build
cmake ..
make

# 运行
./server

目录结构

## 目录结构

```text
TinyReactor/
├── include/          # 头文件
│   ├── buffer.h
│   ├── blockdeque.h
│   ├── log.h
│   ├── threadpool.h
│   ├── heaptimer.h
│   ├── sqlconnpool.h
│   ├── sqlconnRAII.h
│   ├── httprequest.h
│   ├── httpresponse.h
│   ├── httpconn.h
│   ├── userservice.h
│   ├── epoller.h
│   └── webserver.h
├── src/              # 实现文件
│   ├── buffer.cpp
│   ├── log.cpp
│   ├── heaptimer.cpp
│   ├── sqlconnpool.cpp
│   ├── httprequest.cpp
│   ├── httpresponse.cpp
│   ├── httpconn.cpp
│   ├── userservice.cpp
│   ├── epoller.cpp
│   ├── webserver.cpp
│   └── main.cpp
├── resources/        # 静态资源目录
├── log/              # 运行时日志文件
├── CMakeLists.txt
├── Dockerfile
└── docker-compose.yml

已实现模块

Buffer — 动态缓冲区

基于 std::vector<char> 实现的动态字节缓冲区,用 readPos_writePos_ 两个位置标记把连续内存逻辑上划分为三段:已读区、可读区、可写区。

核心设计:

  • Append() 往尾部追加数据,空间不足时自动扩容或整理碎片(MakeSpace
  • Retrieve() 消费数据,本质是移动 readPos_,不做内存拷贝
  • ReadFd() 使用 readv 分散读,栈上临时缓冲区兜底,一次 syscall 读尽数据
  • WriteFd() 把可读区数据直接 write 到 fd

在 Log 模块中作为格式化工作台复用,每条日志拼好后整体取走,Buffer 清空备用。

BlockDeque — 线程安全阻塞队列

模板类,std::deque<T> 加锁封装,实现生产者/消费者模型。

核心设计:

  • 两个 condition_variable 分别管理"不空"和"不满"两个等待条件,职责清晰
  • push_back()unique_lock + wait,队满自动阻塞
  • pop() 返回 std::optional<T>(C++17),队空阻塞,队列关闭返回 std::nullopt,比输出参数写法更现代
  • Close() 先加锁置关闭标志,再 notify_all 唤醒所有阻塞线程安全退出
  • 显式禁用拷贝构造和拷贝赋值(内部持有 mutex,不可拷贝)

Log — 异步日志系统

单例模式,支持同步和异步两种写入模式,对业务线程无感知。

核心设计:

  • 异步模式:业务线程调 write() 只把格式化好的字符串推进 BlockDeque,立即返回;后台写线程 AsyncWrite_() 循环消费队列写文件,两者完全解耦
  • 同步模式maxQueueSize = 0 时不创建队列和写线程,直接 fputs 写文件
  • write() 内部用 Buffer 拼装完整日志行(时间戳 + 级别前缀 + 用户消息),vsnprintf 返回值做钳制处理,防止越界
  • 时间函数使用 localtime_r(线程安全),而非标准库的 localtime
  • 按天自动切割日志文件,单文件超过 50000 行时按编号新建
  • 对外暴露四个宏 LOG_DEBUG / LOG_INFO / LOG_WARN / LOG_ERROR,用 do { } while(0) 包裹保证在任意语法位置安全展开

日志格式:

2026-04-22 08:50:44.472649 [info] : server starting, port = 1316

ThreadPool — 线程池

基于生产者/消费者模型实现的固定大小线程池,所有工作线程共享一个任务队列。

核心设计:

  • struct Pool 把锁、条件变量、关闭标志、任务队列打包,通过 std::shared_ptr<Pool> 在线程池对象和所有工作线程之间共享,保证线程池析构后工作线程仍能安全访问数据直到真正退出
  • 工作线程逻辑直接写在构造函数的 lambda 里:持锁等待 → 有任务则取出解锁执行 → 执行完重新加锁回到等待;锁只保护队列操作,执行任务期间不持锁,保证并发
  • AddTask() 用万能引用 F&& + std::forward 完美转发,支持 lambda、函数指针等所有可调用对象,零拷贝传入队列
  • 工作线程用 detach 独立运行,生命周期由 shared_ptr 引用计数保证
  • 纯头文件实现(threadpool.h),无需 .cpp

HeapTimer — 连接超时管理器

基于最小堆实现的定时器,管理所有连接的超时,超时的连接自动触发回调关闭。

核心设计:

  • std::vector<TimerNode> 数组模拟最小堆,堆顶永远是最快过期的连接,tick() 只需看堆顶,不用遍历全部
  • std::unordered_map<int, size_t> 哈希表记录 fd 到堆中下标的映射,update() 刷新某个连接的超时时间时 O(1) 定位,不需要遍历
  • add() 新连接进来挂号登记,update() 有数据来了把闹钟往后拨,tick() 定期检查超时触发回调,del_() 支持删除堆中任意位置节点(正常完成的连接主动删除)
  • getNextTick() 返回距离下一个超时的毫秒数,直接传给 epoll_wait 的超时参数,让事件循环到时间自动醒来执行 tick()
  • 节点删除用"换到末尾再 pop_back"的方式,避免数组中间删除的移位开销,换上来的节点做 siftDown/siftUp 重新调整位置

SqlConnPool — MySQL 连接池

基于信号量和互斥锁实现的数据库连接池,预先建立固定数量的 MySQL 连接复用,避免每次请求都重新建连的开销。

核心设计:

  • 单例模式,全局唯一一个连接池实例
  • std::queue<MYSQL*> 存放空闲连接,sem_t 信号量记录可用连接数量,取连接时 sem_wait 自动阻塞,还连接时 sem_post 自动唤醒等待线程
  • GetConn() 处理信号量被系统信号打断的情况(EINTR 重试),以及队列意外为空时回滚信号量防止计数错乱
  • ClosePool() 关闭所有连接后在锁外调 mysql_library_end(),不把全局清理操作放在锁保护范围内

配套 SqlConnRAII:RAII 包装器,构造时自动调 GetConn() 取连接,析构时自动调 FreeConn() 还连接,禁用拷贝和移动,保证连接不会被重复归还或泄漏。任何代码路径退出作用域都能安全归还连接。

HTTP 模块 — 请求解析、响应生成、连接处理

HTTP 模块已经完成静态 GET、POST 表单解析、登录注册路由和 MySQL 业务接入。

核心设计:

  • HttpRequest 负责解析请求行、请求头、请求体,支持 application/x-www-form-urlencoded 表单解析
  • HttpResponse 负责生成状态行、响应头、Content-Type、Content-Length,并通过 mmap 映射静态文件
  • HttpConn 负责单个连接的读写流程,串联 Read -> Parse -> HandleRequest -> MakeResponse -> Write
  • UserService 负责登录 / 注册业务,通过 SqlConnRAII 从连接池取连接并访问 MySQL
  • 已通过 socketpair 测试完整链路:POST 请求 -> HttpConn -> UserService -> MySQL -> welcome/error 页面响应

Epoller — I/O 事件通知封装

对 Linux epoll 做轻量 RAII 封装,作为 WebServer 的底层事件通知器。

核心设计:

  • 构造时通过 epoll_create1(0) 创建 epoll 实例,析构时自动 close
  • AddFd / ModFd / DelFd 分别封装 EPOLL_CTL_ADD / MOD / DEL
  • Wait() 封装 epoll_wait,返回本轮就绪事件数量
  • GetEventFd()GetEvents() 用于获取就绪 fd 以及对应事件类型
  • Epoller 只负责事件通知,不负责 accept、读写 socket、HTTP 解析、连接超时或线程调度

已通过 socketpair 最小测试验证:向一端 fd 写入数据,另一端 fd 触发 EPOLLINWait / GetEventFd / GetEvents 能正确返回。

WebServer — Reactor 总调度中心

WebServer 负责把前面所有模块组装成完整服务器,完成监听端口、接收连接、事件分发、线程池处理、定时器超时清理和 HTTP 响应写回。

核心设计:

  • socket / bind / listen 初始化监听 socket,并设置 SO_REUSEADDR
  • 监听 fd 和客户端 fd 都设置为非阻塞,配合 epoll ET 模式使用
  • Epoller 负责事件通知,WebServer 根据 EPOLLIN / EPOLLOUT / EPOLLERR 分发处理
  • HttpConn 负责单连接的 Read / Process / Write
  • ThreadPool 执行读写和请求处理任务,主线程专注事件循环
  • HeapTimer 管理连接超时,连接正常关闭时主动移除 timer
  • 支持静态 GET、POST 登录注册、keep-alive 和连接关闭

已通过真实 TCP 请求验证:GET /GET /login.htmlConnection: keep-alivePOST /register.htmlPOST /login.html

待实现模块

顺序 模块 状态
1 Buffer ✅ 完成
2 Log ✅ 完成
3 ThreadPool ✅ 完成
4 HeapTimer ✅ 完成
5 MySQL 连接池 ✅ 完成
6 HTTP 请求解析 ✅ 完成
7 HTTP 响应封装 ✅ 完成
8 HTTP 连接处理 ✅ 完成
9 登录注册业务 ✅ 完成
10 Epoller ✅ 完成
11 WebServer ✅ 完成

已验证功能

  • GET /:返回首页静态资源
  • GET /login.html:返回登录页面
  • Connection: keep-alive:同一 TCP 连接连续请求成功
  • POST /register.html:注册链路接入 MySQL
  • POST /login.html:登录成功返回 welcome 页面

参考说明

整体架构思路参考 markparticle/WebServer,代码基于 C++17 自行实现,结合工程规范做了调整和改进。

About

TinyReactor 是一个从零手写的 C++ 高性能 Web 服务器,基于 Linux Reactor 网络模型, 旨在深入理解网络编程、多线程并发、I/O 多路复用等核心系统知识。

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors