Linux C++编程之Eventloop

本文会讲到一个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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值