簡介
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對象決定。