muduo网络库学习:基本架构及流程分析
之前一直在学习陈硕大佬的muduo库,一直考虑找个时间总结一下(不然没过多久全忘光了)。muduo库是一种基于Reactor模型的C++网络库,仅支持Linux平台,相关学习可以参考《Linux 多线程服务器端编程》。本文仅做个人学习记录及分享用途,如果错误请您不吝赐教。
基本架构
Basic Reactor
muduo的基础架构采用的就是Reactor模型,最基本的Reactor如下图中所示,简单点来说就是一个I/O线程内部会有一个一直处于循环状态的事件循环(EventLoop),这个EventLoop会一直监听是否有外部的事件触发(比如说客户端向服务器端发来的连接请求),如果有就调用对应的回调函数进行处理。

Mutiple Reactor + ThreadPool
但是更准确点来说,muduo网络库采用的时multiple reactor + threadpool的形式,所谓的multiple reactor,就是指有主从reactor之分,Main Reactor只用于监听新的连接,在accept之后就会将这个连接分配到Sub Reactor上,由子Reactor负责连接的事件处理。
而线程池中维护了两个队列,一个队伍队列,一个线程队列,外部线程将任务添加到任务队列中,如果线程队列非空,则会唤醒其中一只线程进行任务的处理,相当于是生产者和消费者模型。

muduo库的基本使用
这边截取了EchoServer_unittest.cc文件中的部分程序,并且做了一定的简化:
void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp time) {
conn->send(buf);
}
int main(int argc, char* argv[])
{
EventLoop loop; // 创建事件循环
InetAddress listenAddr(2000, false, ipv6); // 创建Server端的地址结构
EchoServer server(&loop, listenAddr); // server绑定所属的EventLoop并指定地址
server.setMessageCallback(onMessage); // 绑定消息到来时的回调函数
server.start(); // 启动线程
loop.loop(); // 开启事件循环
}
首先声明对应的EventLoop类对象,以及使用InetAdderss类构建对应的地址结构,然后在初始化TcpServer类对象中,传入所指向的loop以及需要绑定的地址结构,并server对象中的start方法完成3件事情:
- 启动线程池;
- 在监听用的socket文件描述符(lfd)上启动listen();
- 将监听用的文件描述符的可读事件注册到EventLoop中进行关注。
最后启用了loop.loop()这个来开启整个EventLoop事件循环,由此lfd能够不断监听外来的连接请求。
基本结构介绍
以下内容仅作简要介绍,详细部分建议去看源码,只要我自己复习的时候看得懂就行啦。
EventLoop类
muduo库在使用的时候,会有一个最基本的IO用的线程,内部有且仅有一个EventLoop事件循环,这就是主Reactor。这个事件循环主要就是用来监听是否有新的连接到来,EventLoop类中最主要的就是包含了一个Poller的类对象,这个就是用来实现IO复用的。
// 事件循环,该函数不能跨线程调用,只能在创建该对象的线程中调用
void EventLoop::loop()
{
assert(!looping_);
assertInLoopThread(); // 断言当前处于创建该对象的线程中
looping_ = true; // 标志开始循环
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
LOG_TRACE << "EventLoop " << this << " start looping";
while (!quit_)
{
activeChannels_.clear(); // 将活动通道清除
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); // 调用poll来返回活动的通道,第一个参数是超时时间
++iteration_;
if (Logger::logLevel() <= Logger::TRACE)
{
printActiveChannels(); // 打印活动通道
}
// TODO sort channel by priority
eventHandling_ = true; // 开启事件处理标志位
for (Channel* channel : activeChannels_) // 遍历处理活动通道
{
currentActiveChannel_ = channel;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL; // 全部处理完之后当前正在处理的通道置为空
eventHandling_ = false; // 关闭事件处理标志位
doPendingFunctors();
}
LOG_TRACE << "EventLoop " << this << " stop looping";
looping_ = false; // 停止循环
}
Poller类
Poller类是作为一个基类,会根据环境变量的设置选择是使用epoll还是poll方法,向下有2个派生类,PollPoller和EpollPoller,这也是muduo库中唯一使用面向对象的地方,其余都是基于对象的实现方式。
// DefaultPoller.cc文件中
Poller* Poller::newDefaultPoller(EventLoop* loop)
{
if (::getenv("MUDUO_USE_POLL")) // 查看环境变量是否使用poll
{
return new PollPoller(loop);
}
else
{
return new EPollPoller(loop);
}
}
以EpollPoller为例,分别用epoll中最基本的epoll_create()、epoll_ctl()和epoll_wait()来对内部进行解释。
epoll_create():EpollPoller类的构造函数中就会调用epoll_create()来构建一棵监听用的红黑树。
EPollPoller::EPollPoller(EventLoop* loop)
: Poller(loop),
epollfd_(::epoll_create1(EPOLL_CLOEXEC)),
events_(kInitEventListSize)
{
if (epollfd_ < 0)
{
LOG_SYSFATAL << "EPollPoller::EPollPoller";
}
}
epoll_wait():EpollPoller类内部最主要的就是poll()函数,要求传入超时时间和活跃通道列表。函数内部调用epoll_wait()来返回所有已经就绪的事件个数,然后遍历这些事件,并且为此构建Channel通道,将这些Channel压入到函数传入的活跃通道列表activeChannels中。
Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)

本文深入介绍了muduo网络库的基本架构及其核心组件,包括EventLoop、Poller、Channel等类的作用与实现原理,并详细梳理了客户端连接请求处理流程。
1万+

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



