链接:https://github.com/chenshuo/muduo?tab=readme-ov-file
Muduo 是一个基于 C++11 的多线程
Linux 服务器网络库,采用事件驱动
模式。
它提供了一个简单易用的网络编程接口,支持 TCP 和 UDP 协议
,并且具有良好的性能和可扩展性。
Main Function Points
- 基于
反应堆
模式的事件驱动网络库 - 支持 TCP 和 UDP 协议
- 提供简单易用的网络编程接口
- 具有良好的性能和可扩展性
- 支持多线程编程
Technology Stack
- C++11
- Linux 操作系统
- Boost 库 (仅用于 boost::any)
主要功能
1. 事件驱动架构
- 基于 Reactor 反应堆模式 实现,通过非阻塞 I/O 和多路复用技术(如
epoll
)高效处理网络事件 - 事件循环(EventLoop)机制
自动调度读写事件
,避免轮询造成的 CPU 浪费
2. 协议支持
- 原生支持 TCP 长连接 的
全生命周期
管理(连接建立/数据传输/连接关闭) - 提供 UDP 协议 的异步通信能力,适用于
实时性
要求高的场景
3. 线程模型
- 多线程支持:通过
ThreadPool
实现任务队列与线程资源复用 - 单线程-多进程 与 多线程-单进程 混合模式可选,
平衡并发性能与资源消耗
- 关键组件(如
TcpConnection
)保证线程安全,避免竞态条件
4. 高性能设计
- 零拷贝技术减少数据在内核/用户空间的复制次数
- 对象池(
Buffer
)复用内存资源,降低频繁分配释放的开销 - 实测可承载 10万+ QPS(单机)
5. 开发友好性
- 提供 简洁的 API 接口(如
TcpServer
/TcpClient
封装类) - 内置常用工具:定时器(
Timer
)、日志系统(Logging
)、线程同步原语 - 通过回调机制(
Callback
)实现业务逻辑解耦
补充:基于 C++11 标准开发,依赖 Linux 原生 API,仅使用
boost::any
实现类型擦除。
目录
muduo/
├── CMakeLists.txt # 项目构建配置
├── LICENSE # BSD 许可证文件
├── README.md # 项目说明文档
├── build/ # 编译输出目录
├── cmake/ # CMake 模块配置
├── examples/ # 示例代码
│ ├── asio/chat/ # Boost.Asio 对比示例
│ ├── simple/ # 基础用法示例
│ └── ...
├── muduo/ # 核心库源代码
│ ├── base/ # 基础工具模块
│ │ ├── Date.{h,cc} # 日期时间处理
│ │ ├── Logging.{h,cc} # 日志系统
│ │ └── Thread.{h,cc} # 线程封装
│ └── net/ # 网络核心模块
│ ├── Buffer.{h,cc} # 数据缓冲区
│ ├── Channel.{h,cc} # 事件通道封装
│ ├── EventLoop.{h,cc} # 事件循环核心
│ ├── TcpServer.{h,cc} # TCP 服务端实现
│ └── ...
├── tests/ # 单元测试
│ ├── muduo_base_test # 基础模块测试
│ └── ...
└── util/ # 实用工具
└── ...
说明:
muduo/base/
- 提供线程/日志/时间等基础设施
- 例如
ThreadPool
实现线程复用,LogStream
支持流式日志
muduo/net/
- Reactor 模式核心实现
- 包含
EventLoop
(事件调度器) 和TcpConnection
(连接管理) - 通过
Poller
抽象支持epoll
/poll
等多路复用机制
examples/
- 包含从 Echo 服务到 HTTP 服务器的 20+ 示例
- 每个示例独立演示特定功能模块的用法
💡 实际使用时需注意:
- 编译前需安装 CMake 和 Linux 开发环境
- 通过
./build.sh
脚本一键编译库和示例- 建议在 Ubuntu/CentOS 等主流发行版运行
docs:muduo
Muduo 是一个专为Linux 平台事件驱动型服务器编程设计的 C++ 网络库。
它采用**每个线程一个事件循环
**模型,每个线程拥有自己的事件循环(EventLoop)
以非阻塞方式
处理TCP连接和定时器的 I/O 事件。
该库提供了TCP服务器/客户端
、缓冲区
和 I/O 多路复用
(使用 epoll 或 poll)等抽象,用于简化高性能网络
应用的开发。
目录
引入:配置教程
1. 拉取 muduo 源码
git clone git@github.com:chenshuo/muduo.git
2. 安装依赖
对于 Ubuntu/Debian 系统,执行:
sudo apt update
sudo apt install g++ cmake make libboost-dev
3. 编译 muduo
进入 muduo 目录并编译:
cd muduo
./build.sh
sudo ./build.sh install
如果只需要静态库或动态库,可以只执行
./build.sh
,然后在muduo/build-release
目录下找到编译好的库文件。
4. 编译代码
假设 muduo 安装到了 /usr/local/include
和 /usr/local/lib
,可以这样编译:
g++ test_eventloop.cpp -o test_eventloop -std=c++11 -I/usr/local/include -L/usr/local/lib -lmuduo_net -lmuduo_base -lpthread -lboost_thread -lboost_system
如果没有 sudo ./build.sh install
,可以用如下方式指定 muduo 的头文件和库路径(假设在 muduo/build-release
):
g++ test_eventloop.cpp -o test_eventloop -std=c++11 \
-I./muduo \
-I./muduo/build-release \
-L./muduo/build-release/lib \
-lmuduo_net -lmuduo_base -lpthread -lboost_thread -lboost_system
第一章:事件循环(EventLoop)
欢迎来到 Muduo 网络库系列!
Muduo 是一个专为高并发网络服务设计的 C++ 网络库。
其核心是一个称为 EventLoop
的概念。可以将 EventLoop
视为 Muduo 应用中保持一切顺畅运行的勤勉工蜂。
EventLoop 解决了什么问题?
假设您在构建一个需要同时处理多个客户端的服务器。数据可能在
任何时刻
从任意客户端
连接到达。同时,可能需要在特定时间执行任务
,例如检查空闲连接或发送定期更新
。如何在所有这些连接和定时器之间高效等待任何事件发生,并在
事件发生时快速响应
?一个简单的方法可能是为
每个客户端使用单独的线程,但对于数千个连接来说这会消耗过多资源。另一种方法是不断
在循环中检查每个连接和定时器,但这是低效的(忙等待)。
这正是 EventLoop
的用武之地。
EventLoop:单线程接待员
muduo::net::EventLoop
是 Muduo 应用网络处理的核心组件。
它设计为在**单个专用线程**中运行。其主要职责是等待"事件"
并将其"分发"
到正确的处理代码。
将 EventLoop
想象成办公室中高效的接待员:
- 办公室是
EventLoop
所在的线程 - 接待员(
EventLoop
)在此驻守等待 - 访客到达(网络连接数据到达)或时钟到达特定时间(定时器触发)即为"事件"
- 当事件发生时,接待员
不直接处理
,但清楚知道办公室中谁负责该访客或定时器(即您的"回调"或"处理器"),在同一办公室(线程)内将消息传递给正确负责人
这种"每个线程一个事件循环"模型是 Muduo 的基本原则。
它确保特定 EventLoop
管理的所有操作都在其线程内发生,极大简化了线程安全性。
EventLoop 概念
让我们分解核心思想:
- 单线程单循环:每个
EventLoop
对象严格绑定到创建它的线程。同一线程不能有多个EventLoop
,且EventLoop
必须始终在创建线程中使用。这对避免访问EventLoop
管理的数据结构时的复杂锁
问题至关重要。 - 事件循环周期(
loop()
):EventLoop
的生命周期主要在loop()
方法中度过。该方法进入循环:等待事件
(底层使用epoll
或poll
等机制)- 处理发生的事件(如套接字数据可读、定时器触发)
执行
需要延迟在该线程执行的任务- 循环重复
- 事件源:
EventLoop
等待哪些事件?- 套接字 I/O 事件(如数据可读、写缓冲区可用),通过
Channel
对象管理 - 定时器事件(如"5秒后运行此函数"),通过
TimerQueue
管理 - 其他线程提交的任务
- 套接字 I/O 事件(如数据可读、写缓冲区可用),通过
使用 EventLoop:示例
#include "muduo/net/EventLoop.h"
#include <cstdio>
#include <unistd.h>
void myCallback() {
printf("回调执行! pid = %d\n", getpid());
}
int main() {
printf("主线程启动. pid = %d\n", getpid());
muduo::net::EventLoop loop;
printf("EventLoop 对象 'loop' 已创建\n");
assert(muduo::net::EventLoop::getEventLoopOfCurrentThread() == &loop);
printf("确认 'loop' 属于主线程\n");
printf("调度 2 秒后执行 myCallback...\n");
loop.runAfter(2.0, myCallback);
printf("启动事件循环...\n");
loop.loop();
printf("事件循环结束\n");
return 0;
}
运行
多线程示例
#include "muduo/net/EventLoop.h"
#include "muduo/base/Thread.h"
#include <cstdio>
#include <assert.h>
void threadFunc() {
printf("线程函数启动. pid = %d, tid = %d\n", getpid(), muduo::CurrentThread::tid());
assert(muduo::net::EventLoop::getEventLoopOfCurrentThread() == NULL);
muduo::net::EventLoop loop;
printf("新线程中创建 EventLoop\n");
assert(muduo::net::EventLoop::getEventLoopOfCurrentThread() == &loop);
printf("启动新线程事件循环...\n");
loop.loop();
printf("新线程循环结束\n");
}
int main() {
printf("主线程启动. pid = %d, tid = %d\n", getpid(), muduo::CurrentThread::tid());
muduo::Thread thread(threadFunc);
printf("创建新线程...\n");
thread.start();
thread.join();
printf("子线程结束\n");
return 0;
}
运行
内部机制
EventLoop::loop()
核心流程:
- 轮询等待:通过
Poller
对象高效等待 I/O 事件 - 处理活跃通道:遍历有事件的
Channel
并执行回调 - 执行待处理任务:处理来自其他线程的队列任务
// 简化版 loop() 实现
void EventLoop::loop() {
while (!quit_) {
activeChannels_.clear();
poller_->poll(kPollTimeMs, &activeChannels_); // 阻塞等待
for (Channel* channel : activeChannels_) {
channel->handleEvent(); // 处理事件
}
doPendingFunctors(); // 执行队列任务
}
}
跨线程任务调度
通过 wakeupFd_
实现线程间唤醒机制:
总结
特性 | 描述 | 优势 |
---|---|---|
单线程单循环 | 每个 EventLoop 严格绑定线程 | 简化线程安全 |
loop() 方法 | 核心循环:等待事件 → 处理 → 执行任务 |
高效事件处理 |
Poller | 使用 epoll/poll 进行 I/O 多路复用 | 避免忙等待 |
TimerQueue | 管理定时任务 | 精确时间控制 |
wakeupFd_ | 用于跨线程唤醒的特殊文件描述符 | 实现线程间通信 |
任务队列 | 存储待执行任务的线程安全队列 | 安全跨线程任务提交 |
runInLoop | 智能判断立即执行或排队执行 | 灵活的任务调度机制 |
下一章:线程 →
第二章:线程
在第一章:事件循环中,我们了解到EventLoop
是Muduo网络处理的核心,且必须在专用线程中运行。
虽然可以在主应用线程中运行EventLoop
,但实际服务器通常需要处理多个连接并执行后台任务
,这需要多线程的支持。
在C++中直接使用原始POSIX线程(pthreads
)进行手动线程管理可能非常复杂。这涉及线程创建
、参数传递
、生命周期管理
和正确清理
等问题。这正是muduo::Thread
的价值所在。
Thread解决了什么问题?
muduo::Thread
为底层POSIX线程函数提供了简洁且符合C++风格的封装。
它简化了线程的创建和管理流程,让开发者
专注于要在新线程中运行的函数,而不是繁琐的线程管理代码
。muduo::Thread
的主要目标: 轻松在新线程中启动特定功能函数。这个函数可以是任何逻辑,但在Muduo的上下文中,它通常是
EventLoop
对象的loop()
方法。
Thread:简洁的线程封装器
将muduo::Thread
视为独立执行上下文(线程)的简单容器。
当创建muduo::Thread
对象时,需指定要在新线程中运行的功能函数。
以下是
muduo::Thread
的核心设计理念:
- 封装性:将POSIX线程ID(
pthread_t
)和要运行的函数(ThreadFunc
)打包到单个C++对象中- 简化启动:通过简单的
start()
方法创建底层POSIX线程- 合并等待:
join()
方法允许创建线程,等待新线程完成执行- 命名机制:可为线程赋予有意义的名称,这对调试非常有用(例如在日志或调试器中查看线程名称)
- 上下文传递:处理必要数据(如函数、名称和就绪信号机制)到新线程的入口点
- 异常处理:包含线程入口点的基本异常处理,以捕获意外错误并记录
- 线程ID(
tid
):提供获取操作系统级线程ID(pid_t
)的便捷方式,并通过muduo::CurrentThread::tid()
实现快速访问
使用Thread:在新线程中运行函数
最基本的用例是创建一个函数,并让muduo::Thread
对象运行它。
定义要在单独线程中运行的简单函数:
#include <cstdio>
#include <unistd.h> // getpid
#include "muduo/base/CurrentThread.h" // muduo::CurrentThread::tid()
// 该函数将在新线程中运行
void myThreadFunction() {
printf("进入myThreadFunction. 进程ID: %d, 线程ID: %d\n",
getpid(), muduo::CurrentThread::tid());
// 执行某些工作...例如休眠
sleep(3);
printf("myThreadFunction执行完毕\n");
}
该函数仅打印进程和线程ID,然后休眠几秒。
muduo::CurrentThread::tid()
是Muduo提供的辅助函数,用于获取当前线程的OS级线程ID(pid_t
)。
现在看看如何使用muduo::Thread
运行myThreadFunction
:
#include "muduo/base/Thread.h"
#include "muduo/base/CurrentThread.h"
#include <cstdio>
#include <unistd.h>
// ...(上方myThreadFunction定义)...
int main() {
printf("主线程启动. 进程ID: %d, 线程ID: %d\n",
getpid(), muduo::CurrentThread::tid());
// 1. 创建Thread对象,传入要运行的函数
muduo::Thread thread(myThreadFunction, "MyWorkerThread");
printf("线程对象已创建\n");
// 2. 启动新线程
printf("正在启动新线程...\n");
thread.start();
// 主线程可在此执行其他工作
// printf("主线程正在处理其他任务...\n");
// sleep(1);
// 3. 等待新线程结束
printf("主线程等待新线程结束...\n");
thread.join();
printf("新线程已结束. 主线程退出\n");
return 0;
}
运行此代码时:
main
函数在主线程启动- 打印其PID和TID
- 创建名为
thread
的muduo::Thread
对象,传入myThreadFunction
和名称"MyWorkerThread" - 调用
thread.start()
,创建新的OS线程并开始执行thread
对象管理的代码 - 主线程立即继续执行,调用
thread.join()
等待新线程结束 - 新线程执行
myThreadFunction
,打印不同TID后休眠3秒 - 主线程检测到新线程结束后继续执行
运行:
在线程中运行EventLoop
如第一章所述,Muduo的常见模式是在每个线程中运行一个EventLoop
。
muduo::Thread
是实现此模式的理想工具。
如何在独立线程中运行EventLoop
:
#include "muduo/net/EventLoop.h"
#include "muduo/base/Thread.h"
#include "muduo/base/CurrentThread.h"
#include <cstdio>
#include <assert.h>
// 将在新线程运行的函数
void threadFunc() {
printf("threadFunc启动. 进程ID: %d, 线程ID: %d\n",
getpid(), muduo::CurrentThread::tid());
assert(muduo::net::EventLoop::getEventLoopOfCurrentThread() == NULL);
printf("确认新线程初始无EventLoop\n");
muduo::net::EventLoop loop;
printf("新线程中创建EventLoop对象\n");
assert(muduo::net::EventLoop::getEventLoopOfCurrentThread() == &loop);
printf("确认loop属于当前线程\n");
printf("启动事件循环...\n");
loop.loop(); // 阻塞直到调用quit()
printf("事件循环结束\n");
}
int main() {
printf("主线程启动. 进程ID: %d, 线程ID: %d\n",
getpid(), muduo::CurrentThread::tid());
muduo::Thread thread(threadFunc, "EventLoopThread");
printf("创建新线程...\n");
thread.start();
printf("主线程等待事件循环线程...\n");
thread.join();
printf("事件循环线程结束,主线程退出\n");
return 0;
}
此模式(创建muduo::Thread
运行设置并执行EventLoop
的函数)是Muduo多线程网络I/O应用的基础。
每个此类线程都将拥有自己的EventLoop
。
Thread内部机制解析
muduo::Thread
本质上是POSIX pthread_create
和pthread_join
的封装。以下是调用thread.start()
时的内部流程:
- 数据封装:
start()
在堆上分配detail::ThreadData
辅助对象,存储用户函数、线程名等 - 线程创建:调用
pthread_create
时传入内部入口函数detail::startThread
- 新线程初始化:
- 设置线程名(通过
prctl
系统调用) - 缓存线程ID到
CurrentThread::t_cachedTid
- 通过
CountDownLatch
通知主线程初始化完成
- 设置线程名(通过
- 异常处理:使用try-catch块包裹用户函数调用
- 资源清理:用户函数执行完毕后删除辅助对象
代码:
muduo/base/Thread.h
类定义:
class Thread : noncopyable {
public:
typedef std::function<void ()> ThreadFunc; // 线程函数类型
explicit Thread(ThreadFunc, const string& name = string());
void start(); // 启动线程
int join(); // 等待线程结束
// ...其他成员...
};
muduo/base/Thread.cc
核心逻辑:
// 简化版线程启动逻辑
void Thread::start() {
detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_);
pthread_create(&pthreadId_, NULL, &detail::startThread, data);
latch_.wait(); // 等待新线程初始化完成
}
CurrentThread
命名空间提供线程本地存储:
namespace CurrentThread {
__thread int t_cachedTid; // 线程本地存储的TID缓存
void cacheTid() {
t_cachedTid = detail::gettid(); // 系统调用获取真实TID
}
}
总结
特性 | 描述 | 优势 |
---|---|---|
线程函数封装 | 使用std::function 包装需在新线程执行的代码 |
清晰分离线程定义与任务逻辑 |
原子化启动 | start() 方法一键创建并启动POSIX线程 |
简化线程生命周期管理 |
命名机制 | 支持通过prctl 设置线程名称 |
提升调试效率 |
TID缓存 | CurrentThread::tid() 提供快速线程ID访问 |
避免频繁系统调用 |
异常安全 | 在入口函数中包裹try-catch块 | 防止线程意外终止 |
跨线程同步 | 使用CountDownLatch 确保线程初始化顺序 |
保证资源安全访问 |
muduo::Thread
为C++提供了健壮的线程操作抽象,其核心价值体现在:
- 简化线程管理:封装POSIX线程的复杂API,提供符合RAII原则的接口
- 支持事件循环模型:作为实现"单线程单循环"架构的基础设施
- 增强调试能力:通过线程命名和TID缓存机制提升可观测性
- 保证线程安全:严谨的
初始化顺序控制和异常处理机制
理解muduo::Thread
的工作原理是构建高性能网络应用的关键下一步,我们将探讨EventLoop
如何通过Channel
监听和处理I/O事件。
第三章:通道 →