本文会讲到一个IO多路复用的一个库——eventloop,它用于实现Reactor模型核心功能。
Reactor模型
Reactor模式是处理并发I/O比较常见的一种模式,用于同步I/O,中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有I/O事件到来或是准备就绪(文件描述符或socket可读、写),多路复用器返回并将事先注册的相应I/O事件分发到对应的处理器中。
Reactor是一种事件驱动机制,和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。
简言之,reactor就是由一个中控器去多路复用监听多个事件,事件触发后,中控器将事件转发给某个线程处理。
muduo库
陈硕写了一个支持Reactor模型的网络库,叫做muduo 仓库地址 https://github.com/chenshuo/muduo, 同时也为该库写了一本书叫做《《Linux多线程服务端编程——使用muduoC++网络库》。
EventLoop库
muduo库很大,也很全面,有自己的日志系统。我在做实际项目的时候有时候用不上muduo的全部功能,我仅仅需要其中的eventloop就够了,所以我从中分离除了该eventloop,功能基本相同,但是接口也有一些变化。
源代码放置于仓库 沁明/eventloop (gitee.com), 原作者陈硕。https://github.com/chenshuo/muduo
在eventloop中有三个主要的类:
- Channel,负责一个fd的IO事件分发,但不拥有fd。每一个Channel对象都对应了一个fd,以及绑定了于该fd对应的事件回调函数。
- Poller,是对IO多路复用(即poll、epoll等)的封装。我只实现了其中的epoll部分。
- EventLoop,驱动类,和线程是1:1关系。
EventLoop class
位于文件event_loop.h/cxx中。它持有poller和timers,它的主要功能就是从等待的描述符队列中获取触发的事件,然后调用相应的事件处理函数。
void EventLoop::Loop() {
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
while (!quit_) {
active_channels_.clear();
poll_timestamp_ = poller_->Poll(10000, &active_channels_);
// empty channels for timeout
for (Channel *channel : active_channels_) {
current_channel_ = channel;
current_channel_->HandleEvent(poll_timestamp_);
}
current_channel_ = nullptr;
}
}
poller 查询有哪些channels是激活的——也就是文件描述符有数据了。
再一一执行激活channel的事件处理函数。
Channel class
每个channel对象自始至终只属于一个EventLoop,因此每个Channel对象都只属于某一个IO线程。每个 channel对象自始至终只负责一个文件描述符(fd)的IO事件分发,但它并不拥有这个 fd,也不会在析构的时候关闭这个 fd。
Channel会把不同的IO事件分发为不同的回调,例如ReadCallback、WriteCalback 等,而且“回调”用 std::function 表示,用户无须继承 channel,Channel不是基类。
using EventCallback = std::function<void()>;
using ReadEventCallback = std::function<void(Timestamp)>;
class EventLoop;
// channel state in poller
enum ChannelState {
kChannelStateNone = 0,
kChannelStateEnable,
kChannelStateDisable
};
class Channel : public Noncopyable {
public:
Channel(EventLoop *loop, int fd);
~Channel();
void set_read_callback(ReadEventCallback cb) { read_cb_ = std::move(cb); }
void set_write_callback(EventCallback cb) { write_cb_ = std::move(cb); }
void set_close_callback(EventCallback cb) { close_cb_ = std::move(cb); }
void set_error_callback(EventCallback cb) { error_cb_ = std::move(cb); }
int fd() const { return fd_; }
ChannelState state() const { return state_; }
void set_state(ChannelState s) { state_ = s; }
int events() const { return events_; }
void set_poll_events(int ev) { poll_events_ = ev; }
bool IsNoneEvent() const;
/// Tie this channel to the owner object managed by shared_ptr,
/// prevent the owner object being destroyed in handleEvent.
void Tie(const std::shared_ptr<void> &);
// for Non-blocking fd
void EnableEdgeTrigger();
void DisableEdgeTrigger();
void EnableNonblockReading();
void EnableReading();
void DisableReading();
void EnableWriting();
void DisableWriting();
void DisableAll();
bool IsReading() const;
bool IsWriting() const;
void RemoveFromLoop();
void HandleEvent(Timestamp ts);
private:
void UpdateInLoop();
void HandleEventWithGuard(Timestamp receiveTime);
EventLoop *loop_;
int fd_;
ChannelState state_;
// watching events
int events_;
// occured events returned by poller
int poll_events_;
std::weak_ptr<void> tie_;
bool tied_;
bool event_handling_;
bool added_to_loop_;
ReadEventCallback read_cb_;
EventCallback write_cb_;
EventCallback close_cb_;
EventCallback error_cb_;
};
state_ 代表了当前channel在poller之中的状态(未加入epoll、已加入epoll、被删除等状态)
fd是可读、可写的,读写状态都可以绑定事件执行函数。
在epoll中有边缘触发和水平触发两种模式,channel是支持这两种触发模式的。
events_ 是它关心的IO事件,由用户设置;poll_events_是目前活动的事件,由EventLoop/Poller设置。
当poller获取到相应事件之后,检查poll_events_的事件比特位然后执行对应的回调函数。
void Channel::HandleEvent(Timestamp ts) {
std::shared_ptr<void> guard;
if (tied_) {
guard = tie_.lock();
if (guard) {
HandleEventWithGuard(ts);
}
} else {
HandleEventWithGuard(ts);
}
}
void Channel::HandleEventWithGuard(Timestamp ts) {
event_handling_ = true;
if ((poll_events_ & EPOLLHUP) && !(poll_events_ & EPOLLIN)) {
if (close_cb_)
close_cb_();
}
if (poll_events_ & EPOLLERR) {
if (error_cb_)
error_cb_();
}
if (poll_events_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
if (read_cb_)
read_cb_(ts);
}
if (poll_events_ & EPOLLOUT) {
if (write_cb_)
write_cb_();
}
event_handling_ = false;
}
Poller class
Poller class是IOmultiplexing 的封装。它现在是个具体类,而在muduo中是个抽象基类,因为muduo 同时支持 poll(2)和epoll(4)两种IOmultiplexing机制。Poller 是EventLoop 的间接成员,只供其owner EventLoop 在IO线调用,因此无须加锁。其生命期与EventLoop 相等。Poller 并不拥有 channel。
本EventLoop库只实现了epoll部分。
Poller::poll()是Poller 的核心功能,它调用 epoll获得当前活动的IO事件,然后填充调用方传人的activeChannels,并返回 epoll return 的时刻。这里我们直接把 std::vector<struct epoll_event> events_作为参数传给 poll(2),因为C++标准保证std::vector 的元素排列跟数组一样。C30中的&*events_.begin()是获得元素的首地址,这个表达式的类型为 struct epoll_event*,符合 epoll的要求。
Timestamp EpollPoller::Poll(int timeout, ChannelList *active_channels) {
int numEvents = ::epoll_wait(epoll_fd_, &*events_.begin(),
static_cast<int>(events_.size()), timeout);
int savedErrno = errno;
Timestamp now = Timestamp::Now();
if (numEvents > 0) {
for (int i = 0; i < numEvents; ++i) {
Channel *channel = static_cast<Channel *>(events_[i].data.ptr);
channel->SetPollEvents(events_[i].events);
active_channels->push_back(channel);
}
if ((size_t)numEvents == events_.size()) {
events_.resize(events_.size() * 2);
}
} else if (numEvents == 0) {
;
} else {
// error happens, log uncommon ones
if (savedErrno != EINTR) {
errno = savedErrno;
}
}
return now;
}
timestamp class
一个时间戳的功能类,方便获取当前时间与转化。
Timer class
这是我改写的一个定时器类,和muduo中的实现有些不一样。
一个Timer持有一个channel,用于绑定它的timer fd和callback。
class EventLoop;
class Channel;
class Timestamp;
class Timer : public std::enable_shared_from_this<Timer> {
public:
Timer(EventLoop *eventloop, TimerCallback cb, Timestamp when,
double interval_seconds);
explicit Timer(EventLoop *eventloop, TimerCallback cb, double delay_seconds,
double interval_seconds);
~Timer();
void Stop();
bool Expired();
int fd() const { return timer_fd_; }
void Postpone(double seconds);
void PostponeAfter(double seconds);
private:
void InitTimer(double delay_seconds, double interval_seconds);
void ReadTimer();
EventLoop *eventloop_;
TimerCallback cb_;
int timer_fd_;
std::unique_ptr<Channel> timer_channel_;
};
timer需要恒定的流逝时间,不能因为系统时间改变导致执行异常,所以只能用MONOTONIC参数获取当前时间。
channel 将timer fd加入eventloop,通过EnableNonblockReading() 添加到epoll等待队列,
Timer::Timer(EventLoop *eventloop, TimerCallback cb, Timestamp when,
double interval_seconds)
: eventloop_(eventloop),
cb_(std::move(cb)),
timer_fd_(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)) {
double delay = when - Timestamp::Now();
InitTimer(delay, interval_seconds);
}
Timer::Timer(EventLoop *eventloop, TimerCallback cb, double delay_seconds,
double interval_seconds)
: eventloop_(eventloop),
cb_(std::move(cb)),
timer_fd_(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)) {
InitTimer(delay_seconds, interval_seconds);
}
void Timer::InitTimer(double delay_seconds, double interval_seconds) {
struct itimerspec new_value;
set_timespec_zero(new_value.it_value);
set_timespec_zero(new_value.it_interval);
if (delay_seconds > 0) {
new_value.it_value = doulbe_to_timespec(delay_seconds);
}
if (interval_seconds > 0) {
new_value.it_interval = doulbe_to_timespec(interval_seconds);
}
::timerfd_settime(timer_fd_, 0, &new_value, nullptr);
timer_channel_.reset(new Channel(eventloop_, timer_fd_));
timer_channel_->set_read_callback(std::bind(&Timer::ReadTimer, this));
timer_channel_->EnableNonblockReading();
}
其中这几行代码代表了eventloop的基本用法
timer_channel_.reset(new Channel(eventloop_, timer_fd_));
timer_channel_->set_read_callback(std::bind(&Timer::ReadTimer, this));
timer_channel_->EnableNonblockReading();
用法示例
启动一个eventoop,添加相应事件处理,然后Loop就进入等待了。
int main() {
muduo::event_loop::EventLoop loop;
std::cout << "start "
<< muduo::event_loop::Timestamp::Now().MillisecondsSinceEpoch()
<< std::endl;
loop.RunEveryAfter(5, 20, []() {
std::cout
<< "hello 1 -------- "
<< muduo::event_loop::Timestamp::Now().MillisecondsSinceEpoch()
<< std::endl;
});
loop.RunAfter(2.5, []() {
std::cout
<< "hello 2 ----- "
<< muduo::event_loop::Timestamp::Now().MillisecondsSinceEpoch()
<< std::endl;
});
loop.Loop();
return 0;
}
hello 1, 是在启动20秒之后,每5秒钟执行一次。
hello 2,在启动之后2.5秒执行一次。
执行结果
$ ./test_timer2
start 1691140567093
hello 2 ----- 1691140569565
hello 1 -------- 1691140587065
hello 1 -------- 1691140592065
hello 1 -------- 1691140597065
hello 1 -------- 1691140602065
hello 1 -------- 1691140607065
hello 1 -------- 1691140612065