muduo笔记 网络库(三)事件通道Channel

Poller的存在,是为了监听事件,但具体监听什么事件呢?
这就需要用到Channel类。一个Channel对象绑定了一个fd(文件描述符),用来监听发生在fd上的事件,事件包括空事件(不监听)、可读事件、写完成事件。当fd上被监听事件就绪时,对应Channel对象就会被Poller放入激活队列(activeChannels_),进而在loop循环中调用封装在Channel的相应回调来处理事件。

Channel可以通过EventLoop,向Poller更新自己关心的(监听)事件(通过map Poller::channels_存储)。具体来说,对于PollPoller对象,会同步更新(poll(2))传给内核的poll事件数组pollfds_;对于EPollPoller对象,会同步更新(epoll(7))传递给内核的epoll事件数组events_;

可以这样理解,poll/epoll监听的是fd(上指定的事件pollfd.events),Poller监听的是Channel对象(上指定的事件events_),当监听到事件就绪时,将对应通道加入激活通道队列,在EventLoop的loop循环中依次调用Channel中注册的事件回调。

EventLoop、Poller、Channel这3个类构成了Reactor模式的核心,其时序关系如下图:

img


Channel 类

每个Channel对象从始至终只负责一个文件描述符(fd)的IO事件分发,但不拥有fd,也不会在析构时关闭fd。而是由诸如TcpConnection、Acceptor、EventLoop等,这样需要监听指定文件描述符上事件的类,将fd通过构造函数传递给Channel。
Channel会把不同的IO事件分发为不同的回调,如ReadCallback、WriteCallback,回调对象类型用std::function<>表示,用来定义某个可调用类型。

事件回调类型:

#include <functional>

typedef std::function<void()> EventCallback;
typedef std::function<void(Timestamp)> ReadEventCallback;

Channel成员函数主要包括:
1)设置事件处理的回调函数setCallback(如setReadCallback);
2)使能fd关心的事件events_,可调用enable
(如enableReading),该fd及关心的事件会注册到Poller中进行监听;
3)关闭fd关心的事件events_,可调用disable*(如disableReading),会更新该fd在Poller中监听的事件;
4)关闭fd关心的所有事件events_,可调用disableAll,会更新该fd在Poller中监听的事件;
5)删除对fd的监听,会将其从Poller的ChannelMap中移除;
6)Poller监听到Channel事件被激活时,将其加入到激活列表,在EventLoop中回调handleEvent。

Channel类声明

/**
* Channel绑定一个fd, 用于设置fd上要监听的事件, 以及相应的回调函数.
* Poller监听到有通道绑定的事件发生, 就会将其加入激活的通道列表,
* 然后在EventLoop::loop()中调用该Channel对应事件注册的回调函数
*/
class Channel : private noncopyable
{
public:
    typedef std::function<void()> EventCallback; // 除了读事件, 用于其他事件(如写/关闭/错误)回调类型
    typedef std::function<void(Timestamp)> ReadEventCallback; // 读事件回调类型

    Channel(EventLoop* loop, int fd__);
    ~Channel()

    /* 处理事件, 监听事件激活时, 由EventLoop::loop调用 */
    void handleEvent(Timestamp recevieTime);
    /* 设置事件回调,由Channel对象持有者配置Channel事件回调时调用 */
    void setReadCallback(ReadEventCallback cb)
    { readCallback_ = std::move(cb); }
    void setWriteCallback(EventCallback cb)
    { writeCallback_ = std::move(cb); }
    void setCloseCallback(EventCallback cb)
    { closeCallback_ = std::move(cb); }
    void setErrorCallback(EventCallback cb)
    { errorCallback_ = std::move(cb); }

    /* 将shared_ptr管理的对象系到本地weak_ptr管理的tie_, 可用于保存TcpConnection指针 */
    void tie(const std::shared_ptr<void>&);

    int fd() const { return fd_; }
    int events() const { return events_; }
    void set_revents(int revt) { revents_ = revt; } // used by poller
//    int revents() const { return revents_; }
    bool isNoneEvent() const { return events_ == kNoneEvent; }

    /* 使能/禁用 监听 可读/可写事件, 会影响Poller监听的通道列表 */
    void enableReading() { events_ |= kReadEvent; update(); }
    void disableReading() { events_ &= ~kReadEvent; update(); }
    void enableWriting() { events_ |= kWriteEvent; update(); }
    void disableWriting() { events_ &= ~kWriteEvent; update(); }
    void disableAll() { events_ = kNoneEvent; update(); }
    /* 判断是否请求监听 可写事件 */
    bool isWriting() const { return events_ & kWriteEvent; }
    /* 判断是否请求监听 可读事件 */
    bool isReading() const { return events_ & kReadEvent; }

    // for Poller
    int index() { return index_; }
    void set_index(int idx) { index_ = idx; }

    // for debug
    string reventsToString() const;
    string eventsToString() const;

    void doNotLogHup() { logHup_ = false; }

    EventLoop* ownerLoop() { return loop_; }
    /* 从EventLoop中移除当前通道.
     * 建议在移除前禁用所有事件
     */
    void remove();

private:
    /* 将fd对应事件转化为字符串 */
    static string eventsToString(int fd, int ev);
    /* update()将调用EventLoop::updateChannel更新监听的通道 */
    void update();
    /* 根据不同的事件源激活不同的回调函数,来处理事件 */
    void handleEventWithGuard(Timestamp receiveTime);

    static const int kNoneEvent;
    static const int kReadEvent;
    static const int kWriteEvent;

    EventLoop* loop_;
    const int fd_; // file descriptor
    int events_;   // request events, set by user
    int revents_;  // returned events, current active events, set by EventLoop/Poller
    // used by Poller
    // PollPoller: index of poll fds array mapped to fd_
    // EPollPoller: operation type for fd: kNew, kAdded, kDeleted
    int index_;
    bool logHup_;
    /* 使用weak_ptr指向shared_ptr所指对象, 防止循环引用. 通常是生命周期不确定的对象, 如TcpConnection */
    std::weak_ptr<void> tie_;
    bool tied_; /* weak_ptr tie_绑定对象的标志 */
    bool eventHandling_; /* 正在处理事件的标志 */
    bool addedToLoop_;   /* 加入到loop中, 被监听/处理的标志 */
    ReadEventCallback readCallback_; /* 可读事件回调 */
    EventCallback writeCallback_;    /* 可写事件回调 */
    EventCallback closeCallback_;    /* 关闭事件回调 */
    EventCallback errorCallback_;    /* 错误事件回调 */
};

Channel中的几个重要函数:

handleEvent 处理事件

处理激活的Channel事件,由Poller更新激活的Channel列表,EventLoop::loop()根据激活Channel列表,逐个执行Channel中已注册好的相应回调。实际事件处理工作,由handleEventWithGuard完成。

/**
* 处理激活的Channel事件
* @details Poller中监听到激活事件的Channel后, 将其加入激活Channel列表,
* EventLoop::loop根据激活Channel回调对应事件处理函数.
* @param recevieTime Poller中调用epoll_wait/poll返回后的时间. 用户可能需要该参数.
*/
void Channel::handleEvent(Timestamp recevieTime)
{
    /*
     * shared_ptr通过RAII方式管理对象资源guard
     * weak_ptr::lock可将weak_ptr提升为shared_ptr, 引用计数+1
     */
    std::shared_ptr<void> guard;
    if (tied_)
    {
        /*
         * 为什么使用 tie?
         * 确保在执行事件处理动作时, 所需的对象不会被释放, 但又不能用shared_ptr,
         * 否则可能导致循环引用. 最好使用weak_ptr, 然后lock提升为shared_ptr, 这样更安全.
         */
        guard = tie_.lock();
        if (guard)
        {
            handleEventWithGuard(recevieTime);
        }
    }
    else
    {
        handleEventWithGuard(recevieTime);
    }
}

handleEventWithGuard 识别事件并回调

根据不同的激活原因,调用不的回调函数。这些回调函数,是在持有Channel对象,需要进行事件监听的class中进行设置,比如TcpConnection,EventLoop,Acceptor,TimerQueue等。而有些回调函数,经过层层传递,会呈现可网络库的调用者,比如TcpConnection会将处理一个socket fd的读事件回调(新建连接请求),传递给TcpServer::newConnection,这样用户就能通过TcpServer::setConnectionCallback设置其回调。

/**
* 根据不同的激活原因, 调用不同的回调函数
*/
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
    eventHandling_ = true; // 正在处理事件
    LOG_TRACE << reventsToString(); // 打印fd及就绪事件
    if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
    { // fd挂起(套接字已不在连接中), 并且没有数据可读
        if (logHup_)
        { // 打印挂起log
            LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
        }
        // 调用关闭回调
        if (closeCallback_) closeCallback_();
    }
    if (revents_ & POLLNVAL) // 无效请求, fd没打开
    { // fd dont be opened
        LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
    }
    if (revents_ & (POLLERR | POLLNVAL)) // 错误条件, 或 无效请求, fd没打开
    { // error or fd dont be opened
        if (errorCallback_) errorCallback_();
    }
    if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) // 有待读数据, 或 紧急数据(e.g. TCP带外数据), 或流套接字对端关闭连接/写半连接
    { // there is data, urgent data,  to be read
        if (readCallback_) readCallback_(receiveTime);
    }
    if (revents_ & POLLOUT)
    {
        if (writeCallback_) writeCallback_();
    }
    eventHandling_ = false;
}

update 更新通道

通过EventLoop对象,传递给Poller对象,然后更新其监听的通道列表中对应通道。支持ADD/MOD操作。

void Channel::update()
{
    addedToLoop_ = true;
    loop_->updateChannel(this);
}

void EventLoop::updateChannel(Channel *channel)
{
    assert(channel->ownerLoop() == this);
    assertInLoopThread();
    poller_->updateChannel(channel);
}

/**
* Update array pollfds_
*
* O(logN)
*/
void PollPoller::updateChannel(Channel *channel)
{
    Poller::assertInLoopThread();
    LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
    if (channel->index() < 0)
    { // a new one, add to pollfds_
        // ensure channel point to a new one
        assert(channels_.find(channel->fd()) == channels_.end());
        struct pollfd pfd;
        pfd.fd = channel->fd();
        pfd.events = static_cast<short>(channel->events());
        pfd.revents = 0;
        pollfds_.push_back(pfd);
        int idx = static_cast<int>(pollfds_.size()) - 1;
        channel->set_index(idx);
        channels_[pfd.fd] = channel; // insert (fd, channel)
    }
    else
    { // update existing one
        assert(channels_.find(channel->fd()) != channels_.end());
        assert(channels_[channel->fd()] == channel);
        int idx = channel->index();
        // ensure channel does exist in pollfds_
        assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
        struct pollfd& pfd = pollfds_[idx];
        assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd() - 1);
        pfd.fd = channel->fd();
        pfd.events = static_cast<short>(channel->events());
        pfd.revents = 0;
        if (channel->isNoneEvent())
        {
            // ignore this pollfd
            pfd.fd = -channel->fd() - 1;
        }
    }
}

remove 移除通道

与update类似,也是通过EventLoop传递给Poller对象,将当前通道从Poller的事件列表中删除。支持DEL操作。

void Channel::update()
{
    addedToLoop_ = true;
    loop_->updateChannel(this);
}

void EventLoop::updateChannel(Channel *channel)
{
    assert(channel->ownerLoop() == this);
    assertInLoopThread();
    poller_->updateChannel(channel);
}

/**
* 从监听的通道数组channels_中, 移除指定通道
*/
void PollPoller::removeChannel(Channel *channel)
{
    Poller::assertInLoopThread();
    LOG_TRACE << "fd = " << channel->fd();
    assert(channels_.find(channel->fd()) != channels_.end());
    assert(channels_[channel->fd()] == channel);
    assert(channel->isNoneEvent());
    int idx = channel->index();
    assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
    const struct pollfd& pfd = pollfds_[idx]; (void)pfd;

    // ensure remove one invalid channel from channels_
    assert(pfd.fd == -channel->fd() - 1 && pfd.events == channel->events());
    size_t n = channels_.erase(channel->fd());
    assert(n == 1); (void)n;

    // remove pollfd from pollfds_ by index
    if (implicit_cast<size_t>(idx) == pollfds_.size() - 1)
    { // last of pollfds_
        pollfds_.pop_back();
    }
    else
    {
        // swap the pollfd to be removed with the last of pollfds_,
        // then remove the last
        int channelAtEnd = pollfds_.back().fd;
        iter_swap(pollfds_.begin() + idx, pollfds_.end() - 1);
        if (channelAtEnd < 0)
        {
            channelAtEnd = -channelAtEnd - 1;
        }
        channels_[channelAtEnd]->set_index(idx);
        pollfds_.pop_back();
    }
}

参考

muduo网络库学习(二)对套接字和监听事件的封装Channel | CSDN


muduo库其它部分解析参见:muduo库笔记汇总

posted @ 2022-03-12 16:54  明明1109  阅读(917)  评论(0编辑  收藏  举报