handy : EventBase, EventsImp, Poller, Channel

来源:互联网 发布:超级优化李玄 编辑:程序博客网 时间:2024/06/05 08:54

引言

从handy的example/echo 的执行流程分析handy的实现细节。

https://github.com/yedf/handy/blob/master/handy/event_base.h
https://github.com/yedf/handy/blob/master/handy/event_base.cc

EventBases

struct EventBases: private noncopyable {    virtual EventBase* allocBase() = 0;};

一个纯虚类

  • 包含一个纯虚函数(即接口),该接口返回一个子类EventBase的指针。(感觉怪怪的)
  • 由EventBase继承(名字好像>_<,就差最后一个s)。

疑问:

  • 不知道抽象出EventBases的用意。把基类中的接口直接方法EventBase中不是很直接吗?

EventBase

从给出的注释看,EventBase的功能是:事件派发器,可管理定时器,连接,超时连接。它继承EventBases,必须实现基类定义的接口。

//事件派发器,可管理定时器,连接,超时连接struct EventBase: public EventBases {}

成员变量

public:    std::unique_ptr<EventsImp> imp_;

EventBase只包含一个成员变量,类型为unique_ptr<EventsImp>,注意它是public的。

构造函数

EventBase::EventBase(int taskCapacity) {//taskCapacity指定任务队列的大小,0无限制    imp_.reset(new EventsImp(this, taskCapacity));    imp_->init();}

实际上构造函数new了一个EventsImp,随后调用它的init

疑问:在初始化列表里初始化imp_是不是比较好?这样:

EventBase::EventBase(int taskCapacity)   : imp_(new EventsImp(this, taskCapacity)) {    imp_->init();}

EventBase::loop

实际上调用的是EventsImp::loop

EventsImp

成员变量

struct EventsImp {    EventBase* base_;     PollerBase* poller_;  // Reactor中的Poller,底层应该是使用某种IO Multiplexing,如epoll、kqueue...    std::atomic<bool> exit_;    int wakeupFds_[2];    int nextTimeout_;    SafeQueue<Task> tasks_;    std::map<TimerId, TimerRepeatable> timerReps_;    std::map<TimerId, Task> timers_;    std::atomic<int64_t> timerSeq_;    std::map<int, std::list<IdleNode>> idleConns_;    std::set<TcpConnPtr> reconnectConns_;    bool idleEnabled;...};

EventBase包含EventsImp的unique_ptr;EventsImp包含EventBase的裸指针。
(感觉嵌套好深,还不明白这么做的用意。)

构造函数

EventsImp::EventsImp(EventBase* base, int taskCap):    base_(base), poller_(new PlatformPoller()), exit_(false), nextTimeout_(1<<30), tasks_(taskCap),    timerSeq_(0), idleEnabled(false){}

构造函数创建了poller_: new PlatformPoller()
在编译过程的预处理阶段,会根据平台确定使用哪种poller,如果是Linux,则使用epoll;如果是Mac,则使用kqueue。目前只支持这两个平台。(poller.h)
new PlatformPoller()无非就是调用epoll_create(Linux平台下)创建epoll的fd。

析构函数

EventsImp::~EventsImp() {    delete poller_;    ::close(wakeupFds_[1]);}

注意到poller_在构造函数中创建,在析构函数中销毁。那么为了省去手动delete,是否可以使用智能指针呢?

EventsImp:init

void EventsImp::init() {    // 1. 创建管道,然后向poller注册它的可读事件,用于唤醒IO线程。    int r = pipe(wakeupFds_);    fatalif(r, "pipe failed %d %s", errno, strerror(errno));    r = util::addFdFlag(wakeupFds_[0], FD_CLOEXEC);    fatalif(r, "addFdFlag failed %d %s", errno, strerror(errno));    r = util::addFdFlag(wakeupFds_[1], FD_CLOEXEC);    fatalif(r, "addFdFlag failed %d %s", errno, strerror(errno));    trace("wakeup pipe created %d %d", wakeupFds_[0], wakeupFds_[1]);    // 2. 创建Channel,并注册该fd的可读事件    Channel* ch = new Channel(base_, wakeupFds_[0], kReadEvent);    // 3. 设置可读事件的回调函数,使用lambda    ch->onRead([=] {        char buf[1024];        int r = ch->fd() >= 0 ? ::read(ch->fd(), buf, sizeof buf) : 0;        if (r > 0) {            Task task;            while (tasks_.pop_wait(&task, 0)) {                task();            }        } else if (r == 0) {            delete ch;        } else if (errno == EINTR) {        } else {            fatal("wakeup channel read error %d %d %s", r, errno, strerror(errno));        }    });}

wakeupFds_与muduo中的用意相同,是为了提供一种唤醒IO线程的方法,向epoll(或其他IO Multiplexing)注册wakeupFds_的readable事件。如果IO线程阻塞在epoll_wait上,那么唤醒该线程的方法很简单,就是向wakeupFds_写数据即可。

这里出现的Channel应该也与muduo中的Channel等价。muduo的Channel分析在这里。

EventsImp::loop

void EventsImp::loop() {    while (!exit_)        loop_once(10000);    timerReps_.clear();    timers_.clear();    idleConns_.clear();    for (auto recon: reconnectConns_) { //重连的连接无法通过channel清理,因此单独清理        recon->cleanup(recon);    }    loop_once(0);}

loop又调用loop_once:

void loop_once(int waitMs) {     poller_->loop_once(std::min(waitMs, nextTimeout_));    handleTimeouts(); // 处理连接超时?}

看见了吗,最终使用的是poller::loop_once >_<。 所以在handy,我觉得Poller是最重要的,事件循环在poller中。(muduo中的事件循环在EventLoop中)

每个Channel只负责一个文件描述符fd的IO事件分发,但它不拥有这个fd,也不在析构时关闭该fd。Channel会把不同的IO事件分发给不同的回调函数,如ReadCallback、WriteCallback。

Poller

下面只分析Linux平台下的poller,即epoll。
PollerBase是一个纯虚类,定义了一些接口。PollerEpoll继承PollerBase,并实现了这些接口。

const int kReadEvent = EPOLLIN;const int kWriteEvent = EPOLLOUT;struct PollerEpoll : public PollerBase{    int fd_;    std::set<Channel*> liveChannels_; // 所有监听Channel的指针的集合    //for epoll selected active events    struct epoll_event activeEvs_[kMaxEvents]; // 所有活动的事件,事件的data.ptr保存对应的Channel的指针。    PollerEpoll();    ~PollerEpoll();    void addChannel(Channel* ch) override;    void removeChannel(Channel* ch) override;    void updateChannel(Channel* ch) override;    void loop_once(int waitMs) override;};

liveChannels_ 是一个std::set, 它存放所有监听的Channel的指针(通过addChannel添加)
activeEvs_是epoll_wait返回的就绪事件集合,事件的data.ptr保存者对应Channel的指针

muduo在EventLoop中执行事件循环,handy在Poller中执行事件循环。

PollerEpoll::loop_once

同样只分析Linux下的代码。(最重要、最本质的东西都在这里,首先你要熟悉epoll的基本用法,handy给出了例子)

void PollerEpoll::loop_once(int waitMs) {    int64_t ticks = util::timeMilli();    // 1. epoll_wait 轮询发生的事件,通常阻塞在这里    lastActive_ = epoll_wait(fd_, activeEvs_, kMaxEvents, waitMs);    int64_t used = util::timeMilli()-ticks;    trace("epoll wait %d return %d errno %d used %lld millsecond",          waitMs, lastActive_, errno, (long long)used);    fatalif(lastActive_ == -1 && errno != EINTR, "epoll return error %d %s", errno, strerror(errno));    // 2. 循环处理每个事件    while (--lastActive_ >= 0) {        int i = lastActive_;        // 2.1 注册事件时,已经将Channel的指针传递给了data.ptr,现在发生事件后直接拿来使用        Channel* ch = (Channel*)activeEvs_[i].data.ptr;        // 2.2 根据事件的类型,调用设置好的回调函数处理        int events = activeEvs_[i].events;        if (ch) {            if (events & (kReadEvent | POLLERR)) {                trace("channel %lld fd %d handle read", (long long)ch->id(), ch->fd());                ch->handleRead();            } else if (events & kWriteEvent) {                trace("channel %lld fd %d handle write", (long long)ch->id(), ch->fd());                ch->handleWrite();            } else {                fatal("unexpected poller events");            }        }    }}

Channel

作者的注释挺全的。

//通道,封装了可以进行epoll的一个fdstruct Channel: private noncopyable {    //base为事件管理器,fd为通道内部的fd,events为通道关心的事件    Channel(EventBase* base, int fd, int events);    ~Channel();    EventBase* getBase() { return base_; }    int fd() { return fd_; }    //通道id    int64_t id() { return id_; }    short events() { return events_; }    //关闭通道    void close();    //挂接事件处理器    void onRead(const Task& readcb) { readcb_ = readcb; }    void onWrite(const Task& writecb) { writecb_ = writecb; }    void onRead(Task&& readcb) { readcb_ = std::move(readcb); }    void onWrite(Task&& writecb) { writecb_ = std::move(writecb); }    //启用读写监听    void enableRead(bool enable);    void enableWrite(bool enable);    void enableReadWrite(bool readable, bool writable);    bool readEnabled();    bool writeEnabled();    //处理读写事件    void handleRead() { readcb_(); }    void handleWrite() { writecb_(); }protected:    EventBase* base_;    PollerBase* poller_;    int fd_;    short events_;    int64_t id_;    std::function<void()> readcb_, writecb_, errorcb_;};

构造和析构

Channel::Channel(EventBase* base, int fd, int events): base_(base), fd_(fd), events_(events) {    // 每个Channel有一个独特的id    static atomic<int64_t> id(0);    id_ = ++id;    // 指向poller的指针,使用poller的addChannel将自己加入到poller中    poller_ = base_->imp_->poller_;    poller_->addChannel(this);}Channel::~Channel() {    close(); // channel析构时,调用Channel::close() }void Channel::close() {    if (fd_>=0) {        trace("close channel %ld fd %d", (long)id_, fd_);        poller_->removeChannel(this); // 从poller中移除该channel        ::close(fd_); // 关闭该fd        fd_ = -1;        handleRead(); // 调用read callback    }}

疑问:文什么Channel::close()时需要调用handleRead呢?

PollerEpoll::addChannel

构造Channel时,Channel本身通过传递给它的Poller调用Poller::addChannel,将Channel本身加入到Poller中,底层就是调用epoll_ctl:

void PollerEpoll::addChannel(Channel* ch) {    struct epoll_event ev;    memset(&ev, 0, sizeof(ev));    ev.events = ch->events();    ev.data.ptr = ch; // 保存Channel的指针到epoll_event.data.ptr中备用    trace("adding channel %lld fd %d events %d epoll %d", (long long)ch->id(), ch->fd(), ev.events, fd_);    int r = epoll_ctl(fd_, EPOLL_CTL_ADD, ch->fd(), &ev);    fatalif(r, "epoll_ctl add failed %d %s", errno, strerror(errno));    liveChannels_.insert(ch); // 将Channel的指针保存到Poller的 liveChannels_集合中}

总结

handy事件循环框架

handy的事件循环框架大致是这样的:

EventBase::loop()  poller_->loop_once()    // 1. epoll_wait 轮询发生的事件,设置了超时事件,通常可能阻塞在这里    lastActive_ = epoll_wait(fd_, activeEvs_, kMaxEvents, waitMs);    // 2. 循环处理每个事件    while (--lastActive_ >= 0) {        int i = lastActive_;        // 2.1 注册事件时,已经将Channel的指针传递给了data.ptr,现在发生事件后直接拿来使用        Channel* ch = (Channel*)activeEvs_[i].data.ptr;        // 2.2 根据事件的类型,调用设置好的回调函数处理        int events = activeEvs_[i].events;        if (ch) {            if (events & (kReadEvent | POLLERR)) {                ch->handleRead();            } else if (events & kWriteEvent) {                ch->handleWrite();            } else {                fatal("unexpected poller events");            }        }

handy的事件循环位于poller中。而muduo的事件循环位于EventLoop。个人觉得,muduo的代码结构更直观,当然handy的结构也很自然。

对象生命周期的管理

对象在哪里创建和销毁的? 这个问题需要弄清楚。
(之前学习过muduo中的对象生命周期的管理,比较复杂,需要一定的时间来理解。)

需要弄明白一下问题:

  • EventBase/EventsImp在哪里创建和销毁?
  • Channel在哪里创建和销毁?
  • Poller在哪里创建和销毁?
  • socket在哪里打开?在哪里关闭?

EventBase/EventsImp

从example看,EventBase是一个栈上对象,程序结束时自动销毁。
EventsImp是EventBase的间接成员,使用智能指针unique_ptr管理。

Channel

  • EventBase有一个用于唤醒自己的Channel,该Channel由EventBase自己管理。
  • 在TcpConn中new,在TcpConn的析构函数中delete
  • TODO:补充

Poller

  • Poller是EventsImp的成员变量,在EventsImp构造是new出来的。在EventsImp的析构函数中被delete。
  • Channel会包含所属的Poller的裸指针,使用它来进行事件的添加/修改/删除。(Poller保存所有加入进来的Channel的裸指针)
0 0
原创粉丝点击