moduo的诞生
1. 原始方案:Thread-Per-Connection(BIO)
每当一个客户端连接进来,就创建一个新线程去处理它的 socket。这种模型简单直观,但存在致命缺陷:线程资源有限,1 万个连接就要 1 万个线程,内存和上下文切换开销巨大;而且如果客户端连上来却不发数据,线程就会一直阻塞在 recv() 上,白白浪费资源。
2. 演进方案:IO 多路复用(epoll/select/poll)
为了解决线程爆炸问题,引入了多路转接模型。主线程不再为每个连接创建线程,而是用 epoll 同时监听所有套接字。当某个套接字有事件(新连接、可读、可写)时,才去处理它。这就是事件驱动 IO 的思想:谁有事,处理谁;没事的,不浪费资源。
3. Reactor 模型(单 Reactor 单线程)
把“用 epoll 监听事件 + 事件触发后调用回调处理”这套模式抽象出来,就是 Reactor 模型。Reactor 负责监听和分发事件,Handler 负责具体业务。但单 Reactor 单线程有一个瓶颈:所有事情都在一个线程里干,如果某个 Handler 的业务逻辑很耗时,整个 Reactor 都会被阻塞,其他客户端的事件全部延迟处理,无法利用多核 CPU。
4. 多线程多 Reactor 模型(Main-Sub Reactor)
为了解决单 Reactor 的性能上限,大佬们提出了多 Reactor 模型。核心思想是分工:
-
主线程:只负责监听
listen_fd,接受新连接,然后把新连接的connfd分发给 Sub Reactor。 -
子线程:有多个,每个跑在独立线程里,各自管理一部分连接的读写事件和业务逻辑。
这样,1 万个连接分散到 N 个 子线程 上,每个 Reactor 只管理几千个连接,压力骤减;多个 子线程 并行处理,真正利用多核 CPU。muduo 库采用的就是这种 One Loop Per Thread + 线程池 的 主线程 架构,是 C++ 高并发网络编程的经典范式。
moduo是什么
Muduo库是一个基于非阻塞IO和事件驱动的C++高并发TCP 网络编程库,它是一款基于主从Reactor模型的网络库,其使用的线程模型是one loop per thread,所谓的one loop per thread指的是:
一个线程只能有一个事件循环(EventLoop),用于响应计时器和IO事件。
一个文件描述符只能由一个线程进行读写,换句话说就是一个TCP连接必须归属于某个EventLoop管理
详解:
-
非阻塞IO + 事件驱动:意思就是“你不找我,我不理你;你一来数据,我就立刻处理”。
-
主从 Reactor 模型:就是“主线程接客,子线程干活”。
-
one loop per thread:一个线程配一个事件循环
-
一个文件描述符只能由一个线程进行读写

Muduo库接口介绍
TcpServer类基础介绍
主要接口
TcpServer{
void start();//启动服务器
setConnectionCallback();//设置连接建立/关闭时的回调函数
setMessageCallback();//设置消息处理回调函数
}
setConnectionCallback();
eg:
你开了一家餐厅,你雇佣了一个迎宾(muduo 库的 Acceptor)。
你给迎宾发了一本《迎宾与送客手册》,这就是
setConnectionCallback。手册里写了:
如果客人来了(
conn->connected() == true):
你就说:“欢迎光临!”(打印日志)
带客人去空桌子坐下(分配资源)
把菜单递给他(准备接收数据)
如果客人走了(
conn->connected() == false):
你就说:“欢迎下次再来!”(打印日志)
把桌子收拾干净(释放资源)
把客人从名单上划掉(
_conn.reset())代码长这样子
void onConnection(const TcpConnectionPtr &conn) { if (conn->connected()) { // 客人来了,执行“迎宾”操作 } else { // 客人走了,执行“送客”操作 } }触发时机: 客户端成功连接服务器时,或者客户端断开连接时,muduo 自动帮你调用一次。
setMessageCallback()
eg:
客人坐下后,他随时可能举手喊:“服务员,点菜!”(发来数据)。
你作为服务员,不能一直盯着他看(阻塞),所以你提前写好了一本《点菜与上菜手册》,这就是setMessageCallback。手册里写了:
当客人举手时(收到数据):
你走过去,拿出纸笔,把他要点的菜记下来(
buf->retrieveAllAsString()读取数据)把菜名传给后厨(处理业务逻辑,比如查字典)
后厨做好菜后,你端给客人(
conn->send(res)发送数据)在代码里长这样:
void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time) { // 客人说话了(收到数据) std::string msg = buf->retrieveAllAsString(); // 记下客人说了啥 // 处理业务逻辑... conn->send(res); // 给客人回应(上菜) }触发时机: 只要客户端发来数据,muduo 就会自动帮你调用一次。客户端发 100 次,它就调用 100 次。
但是TCP只是一个server,大佬在涉及的时候就把server和事件监控分开了
EventLoop类基础介绍
事件监控有几个这样重要的接口
EventLoop(){
void loop();//开始事件循环
void quit();//停止循环
Timerld runAfter(delay,cb);//定时任务
}
有上面这这几个接口,我们就可以搭建我们的服务器了,但是还不够,在TcpServer中,有回调函数,它是一个什么的格式呢?
ConnectionCallback :
connectCb(TcpConnectionPtr &conn) //这是连接建立/关闭建立
MessageCallback:
onMessageCb(TcbConnectionPtr &conn, Buffer *buf){
针对conn连接所接收放入到buf中的数据处理
}
通过上面的两个类,我们确实可以完成服务器的搭建,但是我们现在是没办法去进行数据处理的,因为我们对回调函数还是不够熟悉,接下来我们再去熟悉了解一下
TcpConnection类基础介绍
TcpConnection{
void send(std::string &msg);//发送数据
bool connected();//判断当前连接是否连接正常
void shutdown();//关闭连接
}
里面还有两个回调函数,但是不需要我们去做处理,当tcp连接的时候,内部就会帮我们去处理了
Buffer类基础介绍
Buffer{
size_t readableBytes(); //获取缓冲区可读数据大小
const char* peek(); //获取缓冲区中数据的起始地址
int32_t peeklnt32() const; //尝试从缓冲区获取4字节数据,进行网络字节序转换为整型,但是数据并不从缓冲区删除
void retrievelnt32(); //数据读取位置向后偏移4字节 本质上就是删除起始位置的4字节数据
int32_t readlnt32();
string retrieveAllAsString(); //从缓冲区取出所有数据,当作string返回,并删除缓冲区中的数据
string retrieveAsString(size_t len); //从缓冲区取出len长度的数据,当作string返回,并删除缓冲区的数据
}
TcpClient类基础介绍
TcpClient{
void connect(); //连接服务器
void disconnect(); //关闭连接
TcpConnectionPtr connection() const //获取客户端对应的TcpConenction连接
// Muduo库的客户端也是通过Eventloop进行IO事件监控IO处理的
void setConnectionCallback(ConnectionCallback cb); //设置连接建立/关闭时的回调函数
void setMessageCallback(); //收到消息的回调处理函数
}
因为void connect(); 是非阻塞接口
所以会出现一种意外情况:
connect连接还没有建立完全的情况下,调用connection接口获取连接,send发送数据
没有连接就可以跑了?明显不合理,所有客户端有一个接口去防止这个操作
//做计数同步操作的类
CountDownLatch{
void wait();//计数大于0就等待(阻塞)
countDown();//计数--,为0时唤醒wait
}
使用示例:字典翻译C/S
服务端
/*
实现一个翻译服务器,客户端发送过来一个单词,返回一个汉语词语
*/
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/net/Buffer.h>
#include <iostream>
#include <string>
#include <unordered_map>
class DictServer{
public:
DictServer(int port): _server(&_baseloop,
muduo::net::InetAddress("0.0.0.0", port),
"DictServer", muduo::net::TcpServer::kReusePort)
{
// 设置连接事件 (连接建立/管理)的回调
_server.setConnectionCallback(std::bind(&DictServer::onConnection, this, std::placeholders::_1));
// 设置连接信息的回调
_server.setMessageCallback(std::bind(&DictServer::onMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
void start(){
_server.start(); //先开始监听
_baseloop.loop(); //开始死循环事件监控
}
public:
void onConnection(const muduo::net::TcpConnectionPtr &conn){
if(conn->connected()){
std::cout << "连接建立!\n";
}else{
std::cout << "连接断开! \n";
}
}
// 回调消息函数
void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp){
static std::unordered_map<std::string, std::string> dict_map = {
{"hello", "你好"},
{"world", "世界"},
{"apple", "苹果"}
};
std::string msg = buf->retrieveAllAsString();
std::string res;
auto it = dict_map.find(msg);
if(it != dict_map.end()){
res = it->second;
}else{
res = "未知单词!";
}
conn->send(res);
};
private:
muduo::net::EventLoop _baseloop;
muduo::net::TcpServer _server;
};
int main()
{
DictServer server(9090);
server.start();
return 0;
}
客户端
#include <muduo/net/TcpClient.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/EventLoopThread.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/net/Buffer.h>
#include <muduo/base/CountDownLatch.h>
#include <iostream>
#include <string>
class DictClient{
public:
DictClient(const std::string &sip, int sport):
_baseloop(_loopthread.startLoop()),
_downlatch(1), //初始化计数器为1,因为为0时才会唤醒
_client(_baseloop, muduo::net::InetAddress(sip, sport), "DictClient")
{
// 设置连接事件 (连接建立/管理)的回调
_client.setConnectionCallback(std::bind(&DictClient::onConnection, this, std::placeholders::_1));
// 设置连接信息的回调
_client.setMessageCallback(std::bind(&DictClient::onMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
// 连接服务器
_client.connect();
_downlatch.wait();
// _baseloop.loop(); // 开始事件循环监控
}
bool send(const std::string &msg){
if(_conn->connected() == false){
std::cout << "连接已经断开,发送数据失败!\n";
return false;
}
_conn->send(msg);
}
public:
void onConnection(const muduo::net::TcpConnectionPtr &conn){
if(conn->connected()){
std::cout << "连接建立!\n";
_downlatch.countDown(); // 计数--,为0时唤醒阻塞
_conn = conn;
}else{
std::cout << "连接断开! \n";
_conn.reset();
}
}
// 回调消息函数
void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp){
std::string res = buf->retrieveAllAsString();
std::cout << res << std::endl;
};
private:
muduo::net::TcpConnectionPtr _conn;
muduo::CountDownLatch _downlatch;
muduo::net::EventLoopThread _loopthread;
muduo::net::EventLoop *_baseloop;
muduo::net::TcpClient _client;
};
int main()
{
DictClient client("127.0.0.1", 9090);
while(1){
std::string msg;
std::cin >> msg;
client.send(msg);
}
return 0;
}
184



被折叠的 条评论
为什么被折叠?



