muduo网络库学习总结:基本架构及流程分析

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


之前一直在学习陈硕大佬的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件事情:

  1. 启动线程池;
  2. 在监听用的socket文件描述符(lfd)上启动listen();
  3. 将监听用的文件描述符的可读事件注册到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)
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值