Poller
在Muduo中,Poller负责基于IO多路复用机制进行IO事件监听和处理的组件,作为EPollPoller的基类,为后者提供了与PollPoller统一的IO复用接口,并且声明了一个关键的创建派生类的成员函数:
static Poller *newDefaultPoller(EventLoop *loop);
此函数可以通过判断 ` ::getenv("MUDUO_USE_POLL") ` 来决定返回一个PollPoller还是EPollPoller,解耦 PollPoller 和 EPollPoller。由于newDefaultPoller成员函数在定义的时候需要派生类EPollPoller已经实现,故不可以将其定义直接写在Poller.cc中,而是另起了一个文件DefaultPoller.cc
Poller.h
#pragma once
#include "noncopyable.h"
#include "Timestamp.h"
#include <vector>
#include <unordered_map>
class Channel;
class EventLoop;
// muduo库中,多路事件分发器的核心IO复用模块
class Poller : noncopyable
{
public:
using ChannelList = std::vector<Channel *>;
Poller(EventLoop *loop);
virtual ~Poller();
/// Polls the I/O events.
/// Must be called in the loop thread.
/// 给所有IO复用保留统一的接口
virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels) = 0;
/// Changes the interested I/O events.
/// Must be called in the loop thread.
virtual void updateChannel(Channel *channel) = 0;
/// Remove the channel, when it destructs.
/// Must be called in the loop thread.
virtual void removeChannel(Channel *channel) = 0;
// 判断是否在当前poller中
virtual bool hasChannel(Channel *channel) const;
// EventLoop可以通过该接口获取默认的IO复用的具体实现
static Poller *newDefaultPoller(EventLoop *loop); // 返回Poller对象,不能直接在Poller.cc中实现
void assertInLoopThread() const
{
// ownerLoop_->assertInLoopThread();
}
protected:
// channel->fd() : Channel
using ChannelMap = std::unordered_map<int, Channel*>;
ChannelMap channels_;
private:
EventLoop *ownerLoop_;
};
Poller.cc
#include "Poller.h"
#include "Channel.h"
Poller::Poller(EventLoop* loop)
: ownerLoop_(loop)
{
}
Poller::~Poller() = default;
bool Poller::hasChannel(Channel* channel) const
{
auto it = channels_.find(channel->fd());
return it != channels_.end() && it->second == channel;
}
DefaultPoller.cc
#include "Poller.h"
#include "EPollPoller.h"
#include <stdlib.h>
// 解耦 PollPoller 和 EPollPoller
Poller* Poller::newDefaultPoller(EventLoop *loop)
{
if (::getenv("MUDUO_USE_POLL"))
{
// return new PollPoller(loop);
}
else
{
return new EPollPoller(loop);
}
return nullptr;
}
EPollPoller
Muduo网络库的EPollPoller模块继承了Poller,是负责基于Linux epoll机制进行IO事件监听和处理的组件,封装了一系列函数用于调用底层的Linux系统调用。在Muduo中,EPollPoller与EventLoop和Channel类一起构成了事件驱动网络编程的基础框架。下面我将详细介绍EPollPoller模块的主要功能和特点。
主要功能
- 事件监听:EPollPoller模块使用Linux的epoll机制来监听一组文件描述符(通常是网络连接)上的IO事件。这些事件包括可读、可写、异常等。
- 事件注册与注销:通过EPollPoller的接口,用户可以将感兴趣的文件描述符及其相关事件注册到epoll中,以便监听这些事件。当不再需要监听某个文件描述符时,也可以将其从epoll中注销。
- 事件分发:当epoll监听到某个文件描述符上有事件发生时,EPollPoller会将这些事件分发到对应的Channel对象上。每个Channel对象都封装了一个文件描述符及其相关的事件处理逻辑。
- 高效性:EPollPoller利用了epoll机制的高效性,能够同时监听大量的文件描述符,并且在有事件发生时只返回有事件发生的文件描述符集合,避免了轮询所有文件描述符的开销。
实现原理
- 创建epoll实例:在初始化时,EPollPoller会epoll_create1函数来创建一个epoll实例,并获取一个文件描述符用于后续操作。
- 注册事件:使用epoll_ctl函数将需要监听的文件描述符及其关注的事件类型注册到epoll实例中。可以添加新的文件描述符、修改已注册的文件描述符的事件类型或删除已注册的文件描述符。
- 进入事件循环:使用epoll_wait函数在epoll实例上进行阻塞等待,直到有事件发生。当有事件发生时,epoll_wait会返回发生事件的文件描述符集合及其对应的事件类型。
- 事件分发:EPollPoller根据epoll_wait返回的文件描述符集合和事件类型,通过在epoll_event的data.ptr字段预先设置Channel对象的地址,从而能够找到对应的Channel对象,并调用其上的回调函数来处理事件。
struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ } __EPOLL_PACKED;
与其他组件的关系
在Muduo网络库中,EPollPoller与EventLoop和Channel类紧密合作,共同实现了事件驱动的网络编程框架。EventLoop负责驱动整个事件循环,定时检查EPollPoller是否有事件发生,并调用相应的处理逻辑。Channel类则是对文件描述符及其相关事件的封装,它包含了与文件描述符相关的事件处理逻辑和回调函数。
通过这三个组件的协同工作,Muduo网络库能够高效、灵活地处理大量的网络连接和IO事件,为构建高性能的网络服务器提供了坚实的基础。
EPollPoller.h
#include "Poller.h"
#include <sys/epoll.h>
/*
epoll的使用
epoll_create 创建fd
epoll_ctl add/mod/del
epoll_wait
*/
class Channel;
class EPollPoller : public Poller
{
public:
EPollPoller(EventLoop *loop);
~EPollPoller() override; // override 表示由编译器确保父类的同名函数是虚函数
// 重写基类的抽象方法
Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;
void updateChannel(Channel *channel) override;
void removeChannel(Channel *channel) override;
private:
static const int kInitEventListSize = 16; // EventList初始长度
// 填写活跃的连接
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
// 更新channel通道
void update(int operation, Channel *channel);
using EventList = std::vector<epoll_event>;
int epollfd_;
EventList events_;
};
EpollPoller.cc
#include "EventLoop.h"
#include "LogStream.h"
#include "Poller.h"
#include "Channel.h"
#include <sys/eventfd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <iostream>
// 防止一个线程创建多个EventLoop threadLocal
__thread EventLoop *t_loopInThisThread = nullptr;
// 定义Poller超时时间
const int kPollTimeMs = 10000;
// 创建weakupfd,用来notify唤醒subReactor处理新来的channel
int createEventfd()
{
int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (evtfd < 0)
{
LOG_FATAL << "Failed in eventfd" << errno;
}
return evtfd;
}
EventLoop::EventLoop()
: looping_(false),
quit_(false),
callingPendingFunctors_(false),
threadId_(CurrentThread::tid()),
poller_(Poller::newDefaultPoller(this)),
wakeupFd_(createEventfd()),
wakeupChannel_(new Channel(this, wakeupFd_))
{
LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
if (t_loopInThisThread)
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}
else
{
t_loopInThisThread = this;
}
// 设置weakupfd的事件类型以及发生事件后的回调操作
wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
// we are always reading the wakeupfd
// 每一个EventLoop都将监听weakupChannel的EPOLLIN读事件了
// 作用是subloop在阻塞时能够被mainLoop通过weakupfd唤醒
wakeupChannel_->enableReading();
}
EventLoop::~EventLoop()
{
LOG_DEBUG << "EventLoop " << this << " of thread " << threadId_
<< " destructs in thread " << CurrentThread::tid();
wakeupChannel_->disableAll();
wakeupChannel_->remove();
::close(wakeupFd_);
t_loopInThisThread = NULL;
}
void EventLoop::handleRead()
{
uint64_t one = 1;
ssize_t n = read(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
}
}
void EventLoop::loop()
{
looping_ = true;
quit_ = false;
LOG_INFO << "EventLoop " << this << " start looping";
while (!quit_)
{
activeChannels_.clear();
// 当前EventLoop的Poll,监听两类fd,client的fd(正常通信的,在baseloop中)和 weakupfd(mainLoop 和 subLoop 通信用来唤醒sub的)
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
for (Channel *channel : activeChannels_)
{
// Poller监听哪些channel发生事件了,然后上报给EventLoop,通知channel处理相应的事件
channel->handleEvent(pollReturnTime_);
}
// 执行当前EventLoop事件循环需要处理的回调操作
/**
* IO线程 mainLoop 只 accept 然后返回client通信用的fd <= 用channel打包 并分发给 subloop
* mainLoop事先注册一个回调cb(需要subLoop来执行),weakup subloop后,
* 执行下面的方法,执行之前mainLoop注册的cb操作(一个或多个)
*/
doPendingFunctors();
}
LOG_INFO << "EventLoop " << this << " stop looping";
looping_ = false;
}
/**
* 退出事件循环
* 1、loop在自己的线程中 调用quit,此时肯定没有阻塞在poll中
* 2、在其他线程中调用quit,如在subloop(woker)中调用mainLoop(IO)的qiut
*
* mainLoop
*
* Muduo库没有 生产者-消费者线程安全的队列 存储Channel
* 直接使用wakeupfd进行线程间的唤醒
*
* subLoop1 subLoop2 subLoop3
*/
void EventLoop::quit()
{
quit_ = true;
// 2中,此时,若当前woker线程不等于mainLoop线程,将本线程在poll中唤醒
if (!isInLoopThread())
{
wakeup();
}
}
void EventLoop::runInLoop(Functor cb)
{
// LOG_DEBUG<<"EventLoop::runInLoop cb:" << (cb != 0);
if (isInLoopThread()) // 产生段错误
{ // 在当前loop线程中 执行cb
LOG_DEBUG << "在当前loop线程中 执行cb";
cb();
}
else
{ // 在其他loop线程执行cb,需要唤醒其loop所在线程,执行cb
LOG_DEBUG << "在其他loop线程执行cb,需要唤醒其loop所在线程,执行cb";
queueInLoop(cb);
}
}
void EventLoop::queueInLoop(Functor cb)
{
{
std::unique_lock<std::mutex> ulock(mutex_);
pendingFunctors_.emplace_back(cb);
}
// 唤醒相应的,需要执行上面回调操作的loop线程
// 若当前线程正在执行回调doPendingFunctors,但是又有了新的回调cb
// 防止执行完回调后又阻塞在poll上无法执行新cb,所以预先wakeup写入一个数据
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup(); // 唤醒loop所在线程
}
}
// 用来唤醒loop所在的线程,向wakeupfd写一个数据,wakeupChannel就发生读事件,当前loop线程就会被唤醒
void EventLoop::wakeup()
{
uint64_t one = 1;
ssize_t n = ::write(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
}
}
void EventLoop::updateChannel(Channel *channel)
{
// channel是发起方,通过loop调用poll
poller_->updateChannel(channel);
}
void EventLoop::removeChannel(Channel *channel)
{
// channel是发起方,通过loop调用poll
poller_->removeChannel(channel);
}
bool EventLoop::hasChannel(Channel *channel)
{
return poller_->hasChannel(channel);
}
// 执行回调,由TcpServer提供的回调函数
void EventLoop::doPendingFunctors()
{
std::vector<Functor> functors;
callingPendingFunctors_ = true; // 正在执行回调操作
{ // 使用swap,将原pendingFunctors_置空并且释放,其他线程不会因为pendingFunctors_阻塞
std::unique_lock<std::mutex> lock(mutex_);
functors.swap(pendingFunctors_);
}
for (const Functor &functor : functors)
{
functor(); // 执行当前loop需要的回调操作
}
callingPendingFunctors_ = false;
}