C++ Muduo 库学习笔记

Muduo 简介

Muduo 是陈硕写的一个基于 Reactor 模型的 C++ 网络库,适合写高性能网络服务,如 HTTP Server、游戏服务器等。

  • Reactor 模型:是一个事件驱动模型,当 IO 准备好(可读 / 可写)时,由内核通知,再去处理。核心思想是不阻塞,不轮询,全部基于回调。

Muduo 具有以下核心特点:

  • 非阻塞 I/O + epoll(在 Mac 上用 kqueue):不管有没有数据,都立刻返回,不会卡住。
  • 多线程 + one loop per thread 模型:epoll 是 Linux 下的高性能 I/O 多路复用机制,可以监听成千上万个 socket,只要其中一个有数据就通知,效率极高。每个线程只运行一个 epoll,且只处理 I/O,不做业务阻塞逻辑。
  • 基于 TCP 的高层封装,提供 TcpServer/TcpClient:不需要写 socket、bind、listen、accept 等。
  • C++ 回调式 API
  • 线程安全、延迟队列、Buffer、日志库等配套设施

Muduo 使用方式

注意:以下均在 Linux 下使用。

安装

Muduo 依赖于 Boost 库,我们要先安装 Boost。

先把 Linux 系统下的 boost 源码 boost_1_69_0.tar.gz 通过命令下载下来,然后解压,如下:

1
2
wget https://www.programmercarl.com/download/boost_1_69_0.tar.gz
tar -zxvf boost_1_69_0.tar.gz

tar 解压完成后,进入源码文件目录,查看内容:

1
cd boost_1_69_0

运行 bootstrap.sh 工程编译构建程序,需要等待一会。查看目录:

1
./bootstrap.sh

后续要执行的命令:

1
2
3
./b2
sudo ./b2 install
cd ..

然后是安装 Muduo,安装命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
wget https://www.programmercarl.com/download/muduo-master.zip
sudo apt install unzip
unzip muduo-master.zip
cd muduo-master
sudo chmod +x build.sh
./build.sh
./build.sh install
cd ..
cd build
cd release-install-cpp11
cd include
sudo mv muduo/ /usr/include/
cd ..
cd lib/
sudo mv * /usr/local/lib

编译运行

以上环境配置好后,因为我们使用的是第三方库,代码编译完成后需要链接相应的 .so 库,可以通过下面两种不同的方式链接:

  1. 可以直接终端命令行上链接
1
g++ -o server muduo_server.cpp -lmuduo.net -lmuduo_base -lpthread
  1. 由于之前配置时我们已经加入到 /usr/local/lib 了,因此可以直接 include。

服务器编程

muduo 库服务器编程流程:

  1. 组合 TcpServer 对象
  2. 创建 EventLoop 事件循环对象的指针,可以向 loop 上注册感兴趣的事件,相应事件发生 loop 会上报给我们
  3. 明确 TcpServer 构造函数需要的参数,输出服务器对应类的构造函数;
1
2
3
4
5
TcpServer(EventLoop* loop, // 事件循环
const InetAddress& listenAddr, // 绑定 IP 地址 + 端口号
const string& nameArg, // TcpServer 服务器名字
Option option = kNoReusePort // tcp 协议选项
);
  1. 在当前服务器类的构造函数中,注册处理连接断开的回调函数和处理读写事件的回调函数主要通过下面两个回调函数实现:
1
2
3
4
5
void setConnectionCallback(const ConnectionCallback& cb) // 链接的创建与断开
{ connectionCallback_ = cb; }

void setMessageCallback(const MessageCallback& cb) // 消息读写事件
{ messageCallback_ = cb; }

简单说一下回调逻辑:也就是你写的处理逻辑被 Muduo 在事件触发时自动调用。

这样进行设置,当有新连接或断开连接时,就会调用 cb。

  1. 设置合适的服务器端线程数量,muduo 会自动分配 I/O 线程与工作线程。
  2. 开启事件循环 start()

muduo 服务器端编程代码如下:

这里我们实现的功能是,我们往服务器发送什么,服务器就发回什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <functional>
#include <iostream>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpServer.h>
#include <string>

using namespace std;
using namespace muduo;
using namespace muduo::net;
using namespace placeholders;

// 基于 Muduo 网络库开发服务器程序
class ChatServer {
public:
// 3.明确 TcpServer构造函数所需要的参数,输出服务器对应类的构造函数
ChatServer(EventLoop *loop, const InetAddress &listenAddr,
const string &nameArg) // 事件循环,IP + port,服务器名字
: _server(loop, listenAddr, nameArg), _loop(loop) {
// 4.1 注册用户连接的创建和断开事件的回调
_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this,
_1)); // 利用绑定器绑定成员方法
// onConnection,保持参数与muduo库函数参数一致

// 4.2 注册用户读写事件的回调
_server.setMessageCallback(
std::bind(&ChatServer::onMessage, this, _1, _2,
_3)); //利用绑定器绑定成员方法 onMessage,保持参数与 Muduo
//库函数参数一致
// 5. 设置服务器端的线程数量
_server.setThreadNum(4);
}

// 6. 开启事件循环
void start() { _server.start(); }

private:
// 4.1 专门处理用户的连接与断开
void onConnection(const TcpConnectionPtr &conn) { // 连接
if (conn->connected()) {
cout << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << "state:online" << endl;
} else {
cout << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << "state:offline" << endl;
conn->shutdown();
}
}

// 4.2 专门处理用户读写事件
void onMessage(const TcpConnectionPtr &conn, Buffer *buffer,
Timestamp time) { // 连接,缓冲区,接收到数据的事件信息
string buf = buffer->retrieveAllAsString(); // 将接收数据全部放入字符串中
cout << "recv data:" << buf << " time:" << time.toString() << endl;
conn->send(buf); // 收到什么数据发回去什么数据
}
TcpServer _server; // 1. 组合 TcpServer 对象
EventLoop *_loop; // 2. 创建 EventLoop 事件循环对象的指针
};

int main() {
EventLoop loop; // epoll
InetAddress addr("127.0.0.1", 6000);
ChatServer server(&loop, addr, "ChatServer");
server.start(); // 启动服务:listenfd 通过 epoll_ctl 添加到 epoll 上
loop.loop(); // 类似于 epoll_wait
// 以阻塞的方式等待新用户连接或处理已连接用户的读写事件
return 0;
}

接下来是运行及调试。

1
2
3
4
g++ -std=c++11 -I/usr/include \
test_muduo.cpp \
-L/usr/local/lib -lmuduo_net -lmuduo_base \
-lpthread -o test

然后我们开两个终端,一个运行该代码模拟服务器,另一个使用 telnet 工具尝试连接:

1
telnet 127.0.0.1 6000
image-20251207011352260
image-20251207011407291

可以看到正常回显了。

Muduo 核心方法

整个 Muduo 分为两大模块:

  • base:包含基础设施:线程池、日志、时间类、回调、原子操作等。
  • net:网络库:EventLoop、Channel、Poller、TcpServer、TcpClient 等。

接下来,我们要重点学习 Muduo 库核心七大模块:

EventLoop(事件循环)—— Reactor 的核心

每个线程一个 EventLoop (one loop per thread)

核心作用:

  • 负责处理 I/O 事件(epoll)
  • 调用用户回调(MessageCallback 等)
  • 执行定时器任务(TimerQueue)

必须掌握的方法:

  • loop():事件循环,程序在这里“死循环等待事件”
  • quit():让 event loop 退出
  • runInLoop(cb):在该 EventLoop 所在的线程安全运行一个任务
  • queueInLoop(cb):加入任务队列,异步执行
  • runAfter(x, cb):x 秒后执行回调
  • runEvery(x, cb):每 x 秒执行一次

Channel(通道)——fd的事件管理器

fd(file descriptor,文件描述符),用于标识对象。

每个 TCP 连接底层都有一个 Channel,它负责:

  • fd
  • 关心的事件(EPOLLIN / EPOLLOUT)
  • 回调函数(读、写、关闭、错误)

必须掌握的方法:

  • setReadCallback(cb):注册读事件回调(数据抵达时使用)
  • setWriteCallback(cb):注册写事件回调
  • enableReading():向 Poller 注册 EPOLLIN
  • enableWriting():向 Poller 注册 EPOLLOUT
  • disableWriting():关闭写事件
  • disableAll():移除所有事件

Poller(事件分发器)——封装 epoll/kqueue

关键方法:

  • poll(timeout, activeChannels):等待事件
  • updateChannel(channel):更新 fd 事件
  • removeChannel(channel):删除 fd

TcpServer——服务器对象

这是写服务器的主要接口。

常用方法:

  • start():启动服务器
  • setThreadNum(n):设置 sub-loop(I/O线程)数量
  • setConnectionCallback(cb):连接建立/关闭回调
  • setMessageCallback(cb):消息到达回调

TcpConnection——每个连接一个对象

管理一个 TCP 连接的生命周期,包含:

  • Channel(fd对应)
  • 输入/输出缓冲区
  • 回调函数

必须掌握的方法:

  • send(data):发送数据
  • shutdown():关闭写端(FIN)
  • forceClose():强制关闭
  • setMessageCallback(cb):读事件回调
  • setCloseCallback(cb):写事件回调

Buffer(缓冲区)

Muduo 自己实现的智能 buffer。

常用方法:

  • readFd(fd):从 fd 读入 buffer
  • retrieve(n):取走 n 字节
  • retrieveAll():清空 buffer
  • append(data):追加数据
  • peek():读取但不取走