前言
muduo库是陈硕个人开发基于reactor
模式的tcp网络编程库。本人之前有学习过boost.asio
网络库,故学习一下Muduo
网络库,并分析它们之间的优缺点。
本系列将重点放在以下几件事情:
- 梳理
Muduo
的核心架构设计以及各个模块的职责 - 理解
Muduo
的事件驱动机制 - 理解
Muduo
的多线程模型 - 剖析作者精妙的代码设计思路并且重写其核心代码,将原来依赖
boost
库的地方都替换成C++11语法
下面列出主要讲解的模块:
- 网络相关模块
- Socket
- InetAddress
- TcpConnection
- Acceptor
- TcpServer
- 事件循环相关模块
- EventLoop
- Channel
- Poller
- EpollPoller
- 线程相关模块
- Thread
- EventLoopThread
- EventLoopThreadPool
- 基础模块
- Buffer
- Timestamp
- Logger
概述篇
一、Muduo网络库简介
Muduo
网络库是一个基于非阻塞IO和事件驱动的C++高并发TCP网络库
。它基于Reactor
的事件处理模式,并且采用了one loop per thread
的线程模型,即每个线程只运行了一个事件循环。这种模型使得Muduo
能够充分利用多核CPU的性能,实现高效的网路通信。
Reactor事件处理模式
Reactor是这样的一种模式,它要求主线程只负责监听文件描述符上是否有事件发生,如果有的话立即通知工作线程。除此之外,主线程不做任何其他实质性工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。
二、基于muduo实现简易聊天服务器
实现
在使用muduo
网络库的一个巨大优势在于:
业务逻辑与网络层的解耦。muduo
是一个网络层框架,已经把网络层面的接受新的连接、收发数据等操作都封装好了,开发者可以只去关注业务逻辑而不必花费大量时间在底层网络通信的细节上。
这里介绍下面的代码会用到的两个模块:
- 接受新连接:Muduo 的
TcpServer
模块自动处理新客户端的连接请求。 - 连接的生命周期管理:通过
TcpConnection
管理每个连接的状态(建立、关闭)。
聊天服务器功能:
- 支持多个客户端同时连接。
- 广播消息:当某个客户端发送消息时,服务器将消息广播给所有其他客户端
- 管理客户端连接,支持连接建立和断开通知
类ChatServer
用来抽象这一过程,下面是ChatServer
的声明
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/base/Logging.h>
#include <set>
#include <string>class ChatServer {
public:// ChatServer的构造函数ChatServer(muduo::net::EventLoop* loop,const muduo::net::InetAddress& listenAddr);void start();private:// 连接新的客户端的回调函数void onConnection(const muduo::net::TcpConnectionPtr& conn);// 接受消息的回调函数void onMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buf,muduo::Timestamp time);// 管理Server的组件muduo::net::TcpServer server_;// // 保存连接的客户端 std::set<muduo::net::TcpConnectionPtr> connections_; // 保存连接的客户端
};
下面是ChatServer
的定义
#include "ChatServer.h"using namespace muduo;
using namespace muduo::net;ChatServer::ChatServer(EventLoop* loop, const InetAddress& listenAddr): server_(loop, listenAddr, "ChatServer") {server_.setConnectionCallback(std::bind(&ChatServer::onConnection, this, std::placeholders::_1));server_.setMessageCallback(std::bind(&ChatServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}void ChatServer::start() {server_.start();
}void ChatServer::onConnection(const TcpConnectionPtr& conn) {if (conn->connected()) {LOG_INFO << "New connection from " << conn->peerAddress().toIpPort();connections_.insert(conn);} else {LOG_INFO << "Connection from " << conn->peerAddress().toIpPort() << " closed";connections_.erase(conn);}
}void ChatServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) {std::string msg = buf->retrieveAllAsString();LOG_INFO << "Received message: " << msg;// 将消息广播给所有连接的客户端for (const auto& client : connections_) {client->send(msg);}
}
由上述代码可知,程序员只需要定义好接受新的连接,接受客户端发来的消息的回调函数就可以实现简易的聊天服务器。做到业务逻辑 与 网络层面 的 解耦。
接下来实现main
函数
#include <iostream>
#include "ChatServer.h"int main()
{int port = 10086;muduo::net::InetAddress listenAddr(port); // 监听Addrmuduo::net::EventLoop loop;ChatServer server(&loop, listenAddr);server.start();loop.loop();return 0;
}
cmake_minimum_required(VERSION 3.5.0)
project(QTalk VERSION 0.1.0 LANGUAGES C CXX)add_executable(QTalk
main.cpp
ChatServer.cpp)set(EXECUTABLE_OUTPUT_PATH ../)target_link_libraries(QTalkmuduo_netmuduo_basepthread
)
测试运行
之后将程序运行起来后,利用Linux命令可以快速地进行TCP连接与收发数据:nc localhost 10086
Server会输出两条消息:
第一条消息是muduo
库输出的信息,第二条是在ChatServer::onConnection
即接受新的连接后的回调函数输出的消息。
发送一条消息后,客户端会接收到同样的消息,在服务端这里:
同理,第一条消息是muduo
库输出的消息,第二条是ChatServer::onMessage
即收到新的消息的回调函数输出的消息。
三、muduo的架构设计
muduo
网络库是基于reactor
事件处理模式的TCP网络库。
Reacor模式
Reactor模式的核心为:Event事件
、Reactor
反应堆、Demultiplex
事件分发器、EventHandler
事件处理器
Reactor 模式通过以上组件协作完成事件的处理,以下是其具体流程:
- 事件注册:
- 应用程序将需要监听的事件及对应的事件处理器(EventHandler)注册到
Reactor
中
- 应用程序将需要监听的事件及对应的事件处理器(EventHandler)注册到
- 事件循环:
Reactor
启动循环,调用Demuliplex
等待事件发生
- 事件检测:
- 当事件就绪时,
Demultiplex
将事件返回给Reactor
- 当事件就绪时,
- 事件分发:
Reactor
根据事件类型找到对应的EventHandler
,并触发处理
- 事件处理:
EventHandler
执行具体的业务逻辑,如读取数据、处理消息等。
muduo框架架构解析
Muduo
的架构以one loop per thread
即每个线程都有一个事件循环设计,每个线程都有它自己的Reactor
,负责该线程的所有事件。主线程(main reactor)负责监听新的连接,并把accept
后的socket封装起来交付给其他线程的 sub reactor
处理,之后各个sub reactor
负责与该连接的所有读写事件。
主从Reactor工作流程
主线程(main Reactor)
- 职责:
- 负责监听客户端的连接
- 处理
accept
操作,接受新的客户端连接 - 将接收到的客户端连接进行封装,通过负载均衡算法分配给线程池的
sub Reactor
- 工作流程:
- 主线程监听套接字的可读事件(即有新连接到来)
- 调用
accept
接受新连接 - 将新连接的
socket
封装为TcpConnection
对象 - 将新连接分配给某个工作线程的
EventLoop
工作线程(sub Reactor)
- 职责:
- 独立运行
EventLoop
,负责管理其分配的客户端连接 - 处理连接的读写以及超时等操作
- 独立运行
- 工作流程:
- 主线程将新连接分配给工作线程后,工作线程接管该连接
- 工作线程的
EventLoop
注册该连接的事件(如可读、可写) - 当连接上的事件触发时,调用对应的回调函数处理
- 处理完成后,根据需要更新事件监听状态
线程分配与负载均衡
Muduo
使用线程池ThreadPool
来管理多个工作线程,每个工作线程运行一个EventLoop
。新连接分配策略采用简单的轮询算法(Round-Robin):
- 主线程维护一个线程池,每个线程绑定一个
EventLoop
- 每次接收到新连接后,按照轮询算法选择一个线程,将连接分配到该线程对应的
EventLoop
- 开发人员调用
TcpServer::setThreadNum
来设置工作线程的数量
四、总结
muduo
的One Loop Per Thread
模型通过主从Reactor的组合,合理分配了主线程和工作线程的职责,兼顾了性能与易用性。这种设计能够充分利用多核资源,同时屏蔽了复杂的底层网络细节,为开发者提供了一种简单、高效的开发体验。