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的裸指针)
- handy : EventBase, EventsImp, Poller, Channel
- muduo:Channel、Poller分析
- muduo : Reactor(EventLoop Poller Channel)
- muduo源码分析之EventLoop、Channel、Poller的实现
- Unity3D--EventBase
- Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)
- Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)
- ajax poller
- handy : TcpServer
- handy : TcpConn
- Channel
- channel
- channel
- Channel
- channel
- Channel
- Channel
- tomcat--Poller creation failed
- 集成环信
- MySQL定时任务event,储存过程(定时删除指定时间前90天指定表的数据)
- 树形dp 二叉树版本与多叉树版本
- 层次凝聚聚类法
- CUDA 纹理内存 Error:无法识别texture
- handy : EventBase, EventsImp, Poller, Channel
- 熟悉自定义View
- XML文件
- poj 3275 Ranking The Cows
- UVA 679Dropping Balls
- caffe的protocol buffer使用例子
- 三星手机在返回上一个界面时,会闪现不该出现的界面
- java设计模式之------策略模式
- maven配置jdk版本