一个参考 muduo 核心设计实现的 C++ Reactor 网络库原型,包含 base 基础组件和 net 网络组件,支持异步日志、one loop per thread、多线程 IO、poll 事件分发、 epoll 事件分发、eventfd 跨线程唤醒、timerfd 定时器、非阻塞 TCP 连接管理和 Buffer 收发机制。
本项目主要用于学习和验证 Linux/C++ 高性能网络库的核心机制。
mini-muduo 是一个基于 Reactor 模型的 C++ 网络库原型,目标是实现一个可运行、可压测、可解释的 TCP server 框架。
项目分为两层:
base:日志、线程、同步、时间戳等基础组件net:EventLoop、Channel、Poller、TcpServer、TcpConnection、Buffer等网络组件
当前主要支持服务端 TCP 网络编程,已完成 echo server 示例,并使用自写 epoll ping-pong client 进行多连接压测。
- Reactor 事件驱动模型
- one loop per thread 线程模型
- 基于
poll或epoll的 IO multiplexing eventfd实现跨线程wakeuptimerfd实现定时器TcpServer / TcpConnection连接管理Buffer输入/输出缓冲区- 非阻塞 TCP 读写
outputBuffer_处理write写不完问题EventLoopThreadPool多线程 IOrunInLoop/queueInLoop跨线程任务投递AsyncLogging异步日志- ASan / TSan / fd 泄漏 / 压测验证
.
├── base
│ ├── AsyncLogging.cc
│ ├── AsyncLogging.h
│ ├── Atomic.h
│ ├── Condition.cc
│ ├── Condition.h
│ ├── copyable.h
│ ├── CountDownLatch.cc
│ ├── CountDownLatch.h
│ ├── CurrentThread.cc
│ ├── CurrentThread.h
│ ├── Exception.cc
│ ├── Exception.h
│ ├── FileUtil.cc
│ ├── FileUtil.h
│ ├── LogFile.cc
│ ├── LogFile.h
│ ├── Logging.cc
│ ├── Logging.h
│ ├── LogStream.cc
│ ├── LogStream.h
│ ├── Mutex.h
│ ├── noncopyable.h
│ ├── StringPiece.h
│ ├── Thread.cc
│ ├── Thread.h
│ ├── Timestamp.cc
│ ├── Timestamp.h
│ └── Types.h
├── net
│ ├── Acceptor.cc
│ ├── Acceptor.h
│ ├── Buffer.cc
│ ├── Buffer.h
│ ├── Callbacks.h
│ ├── Channel.cc
│ ├── Channel.h
│ ├── Endian.h
│ ├── EventLoop.cc
│ ├── EventLoop.h
│ ├── EventLoopThread.cc
│ ├── EventLoopThread.h
│ ├── EventLoopThreadPool.cc
│ ├── EventLoopThreadPool.h
│ ├── InetAddress.cc
│ ├── InetAddress.h
│ ├── poller
│ │ ├── DefaultPoller.cc
│ │ ├── EPollPoller.cc
│ │ ├── EPollPoller.h
│ │ ├── PollPoller.cc
│ │ └── PollPoller.h
│ ├── Poller.cc
│ ├── Poller.h
│ ├── Socket.cc
│ ├── Socket.h
│ ├── SocketsOps.cc
│ ├── SocketsOps.h
│ ├── TcpConnection.cc
│ ├── TcpConnection.h
│ ├── TcpServer.cc
│ ├── TcpServer.h
│ ├── Timer.cc
│ ├── Timer.h
│ ├── TimerId.h
│ ├── TimerQueue.cc
│ └── TimerQueue.h
├── examples
│ └── echo.cpp
├── scripts
│ ├── asan_build.sh
│ ├── ping-pong.cc
│ └── run_echo_bech.sh
├── docs
│ ├── benchmark.md
│ └── design_notes.md
├── CMakeLists.txt
└── README.md
TcpServer
├── Acceptor
├── EventLoopThreadPool
│ ├── EventLoopThread
│ └── EventLoop
└── TcpConnection
├── Channel
├── Socket
├── inputBuffer_
└── outputBuffer_
EventLoop
├── Poller / EPollPoller
├── TimerQueue
├── wakeupFd
└── pendingFunctors
本项目采用了one loop per thread 模型。
- main loop 负责监听
socket和accept新连接 - IO loop负责具体连接的读写操作
- 每个
TcpConnection只属于一个EventLoop Channel / Poller操作必须在所属EventLoop线程中进行- 跨线程操作通过
runInLoop/queueInLoop投递
详细设计见 docs/design_notes.md
日志功能模块。LogStream 负责将各类数据格式化写入内部缓冲区,Logger 为日志宏入口,Impl 完成时间、线程号、级别、文件行号等前缀和后缀的格式化,SourceFile 用于截取文件名。
双缓冲异步日志系统。前端线程将日志写入内存 buffer,后端线程批量将 buffer 写入文件,减少直接写磁盘的阻塞。
封装 pthread 的线程、互斥锁、条件变量和倒计时门闩,为并发模块提供基础支持。
轻量级时间点类,以微秒精度记录从 Epoch 到当前的时间,支持时间比较、格式化输出和算术运算。
详细设计见 docs/design_notes.md
封装文件描述符 fd 及其关注的事件和回调。通过 EventLoop 更新 Poller 中的监听事件,并在事件就绪时分发读写、关闭、错误等回调。
I/O 多路复用的抽象基类,底层可切换 poll 或 epoll,管理 fd 到 Channel 的映射及事件注册。
Reactor 核心调度器,通常绑定一个线程。循环调用 Poller::poll() 等待事件,分发活跃的 Channel 回调,并支持定时任务和跨线程任务唤醒。
监听新连接,接受连接后通过回调将 connfd 交给 TcpServer 处理。
服务器主类,持有 Acceptor 和 EventLoopThreadPool。为新连接创建 TcpConnection 并分配合适的 IO 线程,管理所有连接的生命周期。
表示一条已建立的 TCP 连接,负责该连接上的读写、缓冲区管理、状态维护及回调分发,支持优雅关闭。
封装一个运行 EventLoop 的线程,提供启动和退出接口。
IO 线程池,管理一组 EventLoopThread,以轮询方式为新连接分配 EventLoop。
定时器管理模块。基于 timerfd 实现,支持一次性或重复定时任务的添加、到期触发和取消。
服务启动、新连接建立、读写处理等核心流程的详细时序图与说明, 请参阅 docs/design_notes.md。
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug -jcmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF
cmake --build build-release -j或者
./scripts/release_build.shcmake -S . -B build-asan \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_ASAN=ON
cmake --build build-asan -j或者
./scripts/asan_build.sh运行:
ASAN_OPTIONS=abort_on_error=1:detect_leaks=1 ./build-asan/bin/echo_server 9981 4cmake -S . -B build-tsan \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_TSAN=ON
cmake --build build-tsan -j或者
./scripts/tsan_build.sh运行
./build-tsan/bin/echo_server 9981 4运行:
./build-debug/bin/echo_server 9981 4或者 Asan 版本:
ASAN_OPTIONS=abort_on_error=1:detect_leaks=1 ./build-asan/bin/echo_server 9981 4参数含义:
9981 :监听端口
4 : io线程数
压测:
./build-debug/bin/ping_pong 127.0.0.1 9981 64 1000 10参数含义:
127.0.0.1 server ip
9981 server port
64 block size
1000 connections
10 duration seconds
也可以使用脚本:
#构建release版本
./scripts/release_build.sh
#开始压测
#压测5组数据
./scripts/run_echo_becnch.sh测试方式:使用单线程epoll client建立多条TCP连接,对echo server进行ping-pong测试模式压测。每个连接在收到完整echo block后继续发送下一块数据。
该测试主要验证:
TcpConnection生命周期Buffer收发- 非阻塞写
outputBuffer_读写EPOLLOUT开关- 多连接稳定性 不代表极限吞吐
| block size | connections | duration | throughput | messages/s | closed |
|---|---|---|---|---|---|
| 64B | 100 | 10s | 1.44MiB/s | 235597 | 0 |
| 64B | 1000 | 30s | 1.17MiB/s | 576287 | 0 |
| 1KB | 1000 | 30s | 8.82MiB/s | 271325 | 0 |
| 64KB | 100 | 30s | 17.21MiB/s | 8315 | 0 |
| 1MB | 20 | 30s | 16.93MiB/s | 509 | 0 |
测试内容:
- heap-use-free
- stack-use-after-scope
- global-buffer-overflow
- double-free
- memory leak
结果:
ASan 检查:在 64B × 1000 连接、64KB × 100 连接、1MB × 20 连接等 echo 压测场景下,server 无 ASan 报错、无崩溃、无断言失败
测试内容:
- data race
- 线程间未同步读写
- 锁使用错误
结果:
Tsan检查:在64B × 1000 连接、64KB × 100 连接、1MB × 20 连接等 echo 压测场景下,EventLoop / TcpConnection 路径无明显 data race
观察命令:
watch -n 1 'ls /proc/$(pidof echo_server)/fd | wc -l'
验证方式:
压测过程中 fd 数随连接数上升;压测结束后 fd 数回落到初始水平附近。
结果:
压测结束后 fd 正常回落,无明显连接 fd 泄漏。
项目使用 GoogleTest 对部分基础组件进行了单元测试,主要覆盖:
Buffer:append、retrieve、retrieveAll、扩容、索引变化等基础行为LogStream:整数、字符串、指针、边界值等格式化输出Timestamp:时间差计算、时间增加、字符串格式化InetAddress:IP / Port 构造与转换
测试主要用于验证基础组件的不变量和边界行为。复杂网络路径如 TcpConnection 生命周期、EventLoopThreadPool 分发、fd 泄漏等,主要通过 echo benchmark、ASan、TSan 和手动压测验证。
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
cmake --build build-debug -j
ctest --test-dir build-debug --output-on-failureTcpConnection 和 Channel 存在回调期间对象被释放的风险。项目使用 shared_ptr 管理 TcpConnection,并通过 Channel 的 tie weak_ptr 机制保证 handleEvent 期间连接对象存活。
TcpConnection 所属 IO loop 和调用 send/close 的线程可能不同,因此涉及 Channel/Poller 的操作必须回到所属 EventLoop 执行。项目通过 runInLoop / queueInLoop 和 eventfd wakeup 保证跨线程任务被正确投递。
非阻塞 socket 的 write 可能一次写不完,需要 outputBuffer_ 保存剩余数据,并通过 EPOLLOUT 继续发送。写完后必须关闭写事件,避免 EPOLLOUT 空转。
TimerQueue 需要处理定时器未触发、正在触发、重复触发和取消等不同状态,因此使用 timers_、activeTimers_ 和 cancelingTimers_ 管理定时器生命周期。
AsyncLogging 需要在保证日志完整性的同时减少业务线程阻塞。项目通过 currentBuffer_ / nextBuffer_ / buffers_ 和后台线程批量写入降低锁竞争和磁盘 IO 影响。
当前项目主要实现服务端 TCP 网络库核心,暂未实现:
- TcpClient / Connector
- HTTP 协议
- RPC 框架
- SSL/TLS
- 协程
- 生产级性能优化
- 完整 CI 测试
当前压测结果主要用于验证核心链路稳定性,不代表极限性能。
- 基于 mini-muduo 实现 RPC 框架
- 增加更完整的单元测试
- 增加 pipeline benchmark
- 补充 perf / 火焰图分析
- 继续完善文档和示例
- muduo 网络库
- Linux epoll
- eventfd / timerfd
- Reactor 模型
- 《Linux 多线程服务端编程》