zoukankan      html  css  js  c++  java
  • muduo源码分析Channel

     

    简介

    Channel类,即通道类。Channel类是可能产生事件的文件描述符封装在其中的,这里的文件描述符可以是file descriptor,可以是socket,还可以是timefd,signalfd。但实际上它不拥有fd_,不用负责将其关闭,关闭是Eventpool的事情。

    Acceptor和TcpConnection通过Channel与Eventpool建立连接,而且真正的事件处理函数也都是封装在Channel类中的。

    每个Channel对象自始至终只属于一个EventLoop,因此每个Channel对象都只属于某一个IO线程。

    每个Channel对象自始至终只负责一个文件描述符(fd)的IO事件分发,但它并不拥有这个fd,也不会在析构的时候关闭这个fd。

    Muduo用户一般不直接使用Channel,而会使用更上层的封装,如TcpConnection。

    Channel的生命期由其owner calss负责管理。

    Channel的成员函数都只能在IO线程调用,因此更新数据成员都不必加锁。

    源码分析

    Channel.h源码

    // Copyright 2010, Shuo Chen.  All rights reserved.
    // http://code.google.com/p/muduo/
    //
    // Use of this source code is governed by a BSD-style license
    // that can be found in the License file.
    
    // Author: Shuo Chen (chenshuo at chenshuo dot com)
    //
    // This is an internal header file, you should not include this.
    
    #ifndef MUDUO_NET_CHANNEL_H
    #define MUDUO_NET_CHANNEL_H
    
    #include <boost/function.hpp>
    #include <boost/noncopyable.hpp>
    #include <boost/shared_ptr.hpp>
    #include <boost/weak_ptr.hpp>
    
    #include <muduo/base/Timestamp.h>
    /*个人理解:channel是一个具体来处理事件的类,它与eventloop关系紧密,主要是根据事件宏定义来调用对应的回调函数
     *主要的事件有三种,读事件,写事件和结束事件
     **/
    namespace muduo {
        namespace net {
    
            class EventLoop;
    
    ///
    /// A selectable I/O channel.
    ///
    /// This class doesn't own the file descriptor.
    /// The file descriptor could be a socket,
    /// an eventfd, a timerfd, or a signalfd
            class Channel : boost::noncopyable {
            public:
                typedef boost::function<void()> EventCallback;
                typedef boost::function<void(Timestamp)> ReadEventCallback;//读事件的回调函数中必须有参数Timestamp
    
                Channel(EventLoop *loop, int fd);//一个channel要绑定一个EventLoop和一个文件描述符,但channel无权操作fd
                ~Channel();
    
                void handleEvent(Timestamp receiveTime);//处理事件
                void setReadCallback(const ReadEventCallback &cb) { readCallback_ = cb; }
    
                void setWriteCallback(const EventCallback &cb) { writeCallback_ = cb; }
    
                void setCloseCallback(const EventCallback &cb) { closeCallback_ = cb; }
    
                void setErrorCallback(const EventCallback &cb) { errorCallback_ = cb; }//设置四种回调函数
    
                /// Tie this channel to the owner object managed by shared_ptr,
                /// prevent the owner object being destroyed in handleEvent.
                //这个函数,用于延长某些对象的生命期,使其寿命长过Channel::handleEvent()函数。
                void tie(const boost::shared_ptr<void> &);//将一个shared_ptr指针的值赋给tie_
    
                int fd() const { return fd_; }
    
                int events() const { return events_; }
    
                void set_revents(int revt) { revents_ = revt; } // used by pollers
                // int revents() const { return revents_; }
                bool isNoneEvent() const { return events_ == kNoneEvent; }//判断事件是否为0,也就是没有关注的事件
    
                void enableReading() {
                    events_ |= kReadEvent;
                    update();
                }//设置读事件,并将当前channel加入到poll队列当中
                // void disableReading() { events_ &= ~kReadEvent; update(); }
                void enableWriting() {
                    events_ |= kWriteEvent;
                    update();
                }//设置写事件,并将当前channel加入到poll队列当中
                void disableWriting() {
                    events_ &= ~kWriteEvent;
                    update();
                }//关闭写事件,并将当前channel加入到poll队列当中
                void disableAll() {
                    events_ = kNoneEvent;
                    update();
                }//关闭所有事件,并暂时删除当前channel
                bool isWriting() const { return events_ & kWriteEvent; }//是否关注写事件
    
                // for Poller
                int index() { return index_; }//返回序号
                void set_index(int idx) { index_ = idx; }//设置序号
    
                // for debug
                string reventsToString() const;
    
                void doNotLogHup() { logHup_ = false; }//把挂起标志位置false
    
                EventLoop *ownerLoop() { return loop_; }
    
                void remove();
    
            private:
                void update();
    
                void handleEventWithGuard(Timestamp receiveTime);
    
                static const int kNoneEvent;
                static const int kReadEvent;
                static const int kWriteEvent;
    
                EventLoop *loop_;            // 所属EventLoop
                const int fd_;            // 文件描述符,但不负责关闭该文件描述符
                int events_;        // 需要epoll关注的事件
                int revents_;        // poll/epoll wait返回的需要处理的事件
                int index_;        // used by Poller.表示在epoll队列中的状态:1.正在队列中2.曾经在队列中3.从来没在队列中
                bool logHup_;        // for POLLHUP是否被挂起
    
                boost::weak_ptr<void> tie_;//保证channel所在的类
                bool tied_;
                bool eventHandling_;        // 是否处于处理事件中
                ReadEventCallback readCallback_;//当文件描述符产生读事件时,最后调用的读函数,我将它命名为channel的读函数
                EventCallback writeCallback_;//当文件描述符产生写事件时,最后调用的写函数,我将它命名为channel的写函数
                EventCallback closeCallback_;//当文件描述符产生关闭事件时,最后调用的关闭函数,我将它命名为channel的关闭函数
                EventCallback errorCallback_;//当文件描述符产生错误事件时,最后调用的错误函数,我将它命名为channel的错误函数
            };
    
        }
    }
    #endif  // MUDUO_NET_CHANNEL_H

    Channel.cc源码

    // Copyright 2010, Shuo Chen.  All rights reserved.
    // http://code.google.com/p/muduo/
    //
    // Use of this source code is governed by a BSD-style license
    // that can be found in the License file.
    
    // Author: Shuo Chen (chenshuo at chenshuo dot com)
    
    #include <muduo/base/Logging.h>
    #include <muduo/net/Channel.h>
    #include <muduo/net/EventLoop.h>
    
    #include <sstream>
    
    #include <poll.h>
    
    using namespace muduo;
    using namespace muduo::net;
    
    const int Channel::kNoneEvent = 0;
    const int Channel::kReadEvent = POLLIN | POLLPRI;
    const int Channel::kWriteEvent = POLLOUT;
    
    Channel::Channel(EventLoop *loop, int fd__)
            : loop_(loop),
              fd_(fd__),
              events_(0),
              revents_(0),
              index_(-1),//就是kNew
              logHup_(true),
              tied_(false),
              eventHandling_(false) {
    }
    
    Channel::~Channel() {
        assert(!eventHandling_);
    }
    
    void Channel::tie(const boost::shared_ptr<void> &obj)//给tie_指针赋值,tie_指针是一个weak_ptr指针,但是给weak_ptr指针赋值的一定是一个shared_ptr指针
    {
        tie_ = obj;
        tied_ = true;
    }
    
    void Channel::update()//把当前的channel加入到poll队列当中
    {
        loop_->updateChannel(this);
    }
    
    // 调用这个函数之前确保调用disableAll
    // 从EventLoop中移除这个channel
    void Channel::remove() {
        assert(isNoneEvent());
        loop_->removeChannel(this);
    }
    
    void Channel::handleEvent(Timestamp receiveTime)//Timestamp主要用于读事件的回调函数
    {
        boost::shared_ptr<void> guard;
        if (tied_) {
            guard = tie_.lock();//提升tie_为shared_ptr,如果提升成功,说明指向一个存在的对象
            if (guard) {
                LOG_TRACE << "[6] usecount=" << guard.use_count();
                handleEventWithGuard(receiveTime);
                LOG_TRACE << "[12] usecount=" << guard.use_count();
            }
        } else {
            handleEventWithGuard(receiveTime);
        }
    }
    
    void Channel::handleEventWithGuard(Timestamp receiveTime)
    //暂时理解:查看epoll/或者poll返回的具体是什么事件,并根据事件的类型进行相应的处理
    {
        eventHandling_ = true;
        /*
        if (revents_ & POLLHUP)
        {
            LOG_TRACE << "1111111111111111";
        }
        if (revents_ & POLLIN)
        {
            LOG_TRACE << "2222222222222222";
        }
        */
        if ((revents_ & POLLHUP) && !(revents_ & POLLIN))//当事件为挂起并没有可读事件时
        {
            if (logHup_) {
                LOG_WARN << "Channel::handle_event() POLLHUP";
            }
            if (closeCallback_) closeCallback_();
        }
    
        if (revents_ & POLLNVAL)//描述字不是一个打开的文件描述符
        {
            LOG_WARN << "Channel::handle_event() POLLNVAL";
        }
    
        if (revents_ & (POLLERR | POLLNVAL))//发生错误或者描述符不可打开
        {
            if (errorCallback_) errorCallback_();
        }
        if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))//关于读的事件
        {
            if (readCallback_) readCallback_(receiveTime);
        }
        if (revents_ & POLLOUT)//关于写的事件
        {
            if (writeCallback_) writeCallback_();
        }
        eventHandling_ = false;
    }
    
    string Channel::reventsToString() const//把事件编写成一个string
    {
        std::ostringstream oss;
        oss << fd_ << ": ";
        if (revents_ & POLLIN)
            oss << "IN ";
        if (revents_ & POLLPRI)
            oss << "PRI ";
        if (revents_ & POLLOUT)
            oss << "OUT ";
        if (revents_ & POLLHUP)
            oss << "HUP ";
        if (revents_ & POLLRDHUP)
            oss << "RDHUP ";
        if (revents_ & POLLERR)
            oss << "ERR ";
        if (revents_ & POLLNVAL)
            oss << "NVAL ";
    
        return oss.str().c_str();
    }

    Channel::tie()详解

    这里是一个智能指针使用的特定场景之一,用于延长特定对象的生命期

    结合例子分析,看下面的一个调用时序图

     

    当对方断开TCP连接,这个IO事件会触发Channel::handleEvent()调用,后者会调用用户提供的CloseCallback,而用户代码在onClose()中有可能析构Channel对象,这就造成了灾难。等于说Channel::handleEvent()执行到一半的时候,其所属的Channel对象本身被销毁了。这时程序立刻core dump就是最好的结果了。

    Muduo的解决办法是提供Channel::tie(const boost::shared_ptr<void>&)这个函数,用于延长某些对象(可以是Channel对象,也可以是其owner对象)的生命期,使之长过Channel::handleEvent()函数。

    Muduo TcpConnection采用shared_ptr管理对象生命期的原因之一就是因为这个。

    当有关闭事件时,调用流程如下:

    Channel::handleEvent -> TcpConnection::handleClose ->TcpClient::removeConnection ->TcpConnection::connectDestroyed->channel_->remove()。

    1、为了在Channel::handleEvent处理期间,防止因其owner对象被修改,进而导致Channel被析构,最后出现不可预估错误。 Channel::tie()的作用就是将Channel的owner对象进行绑定保护起来。

     2、另外channel->remove的作用是删除channel在Poll中的地址拷贝,而不是销毁channel。channel的销毁由其owner对象决定。

  • 相关阅读:
    RootMotionComputer 根运动计算机
    tar压缩解压缩命令详解
    解决有关flask-socketio中服务端和客户端回调函数callback参数的问题
    flask-sqlalchemy中Datetime的创建时间、修改时间,default,server_default,onupdate
    sqlalchemy和flask-sqlalchemy的几种分页方法
    Flask路由报错:raise FormDataRoutingRedirect(request)
    解决Python自带的json不能序列化data,datetime类型数据问题
    Python中将字典转换为有序列表、无序列表的方法
    flask-sqlalchemy 一对一,一对多,多对多操作
    python2 UnicodeDecodeError: 'ascii' codec can't decode byte 0xce in position 7: ordinal not in range(128)
  • 原文地址:https://www.cnblogs.com/qldabiaoge/p/12700078.html
Copyright © 2011-2022 走看看