当前位置 博文首页 > wanggao的专栏:muduo学习笔记:net部分之Channel
正如其名,muduo中Channel是底层通过Socket建立TCP通信的通道,用于事件分发。Channel对fd的事件相关方法进行了封装,例如负责注册fd的可读或可写事件到EvenLoop,又如fd产生事件后要如何响应。
Channel
与fd是聚合关系,一个fd对应一个channel,不实际拥有fd,Channel析构是不会close当前fd。文件描述符fd可以是socketfd
、eventfd
、timerfd
或signalfd
。
Channel一般作为其他类的成员,不被直接使用,而使用更上层的封装,例如用于服务端的Acceptor,用于客户端的Connector,用于一次客户端服务端通信的TcpConnection。最重要的是,EventLoop通过一个vector<Channel*> 数组对注册到其内的众多fd的管理,在事件循环中能够接收并处理不同类型fd上的事件。
Channel的生命期由其owner calss负责管理,且成员函数都只能在IO线程调用,因此更新数据成员都不必加锁。
在外部使用Channel初始化时,需要指定所属的EvenetLoop,以及外部的fd文件描述符。
fd上的事件实际是外部触发,例如,服务端Acceptor
的监听有新的客户端连接accept(2)
、客户端断开调用close(2)
;客户端Connector
的主动发起连接connect(2)
、断开连接close(2)
等。Channel监测到fd上的事件发生后,调用对应的注册回调函数。
///
/// 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 : noncopyable
{
public:
typedef std::function<void()> EventCallback; // 事件回调函数定义
typedef std::function<void(Timestamp)> ReadEventCallback; // 读事件回调函数定义
Channel(EventLoop* loop, int fd);
~Channel();
void handleEvent(Timestamp receiveTime); // 处理事件
//设置可读、可写、关闭、出错的事件回调函数
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); }
/// 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>&);
int fd() const { return fd_; }
int events() const { return events_; } //返回注册的事件
void set_revents(int revt) { revents_ = revt; } // 设置发生的事件:poller中调用
// int revents() const { return revents_; }
bool isNoneEvent() const { return events_ == kNoneEvent; } // 判断是否注册了事件
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_; }
void remove();
private:
static string eventsToString(int fd, int ev);
void update(); // 注册事件后更新到EventLoop
void handleEventWithGuard(Timestamp receiveTime); // 加锁的事件处理
static const int kNoneEvent;
static const int kReadEvent;
static const int kWriteEvent;
EventLoop* loop_; //channel所属的EventLoop
const int fd_; //channel负责的文件描述符,但不负责关闭该文件描述符
int events_; //注册的事件
int revents_; // poller返回接收到的就绪的事件
int index_; //表示在poll的事件数组中的序号
bool logHup_;
std::weak_ptr<void> tie_;
bool tied_;
bool eventHandling_; // 是否处于处理事件中
bool addedToLoop_;
ReadEventCallback readCallback_; // 读事件回调
EventCallback writeCallback_; // 写事件回调
EventCallback closeCallback_; // 关闭事件回调
EventCallback errorCallback_; // 出错事件回调
};
着重说明其中几个函数。
一旦Channel被创建且注册了读或写事件,需通过EventLoop将对应的fd事件监听交由Poller管理。Channel中更新、关闭fd上注册的事件也需要进行相应EventLoop中的处理。
update()
:把当前的channel加入到poll队列当中,或者更新fd的监听事件remove()
:先关闭fd上注册的事件,并从其所属的EventLoop的Poller中移除当前channelvoid Channel::update()
{
addedToLoop_ = true;
loop_->updateChannel(this);
}
void Channel::remove()
{
assert(isNoneEvent());
addedToLoop_ = false;
loop_->removeChannel(this);
}
是Channel的核心,事件分发。由EventLoop::loop()调用,根据revents_的值分别调用不同的用户回调函数。
void Channel::handleEvent(Timestamp receiveTime) //Timestamp用于读事件的回调函数的参数
{
std::shared_ptr<void> guard;
if (tied_){
guard = tie_.lock();
if (guard) { handleEventWithGuard(receiveTime); }
}
else{ handleEventWithGuard(receiveTime); }
}
// 查看epoll/或者poll返回的具体是什么事件,并根据事件的类型进行相应的处理
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
LOG_TRACE << reventsToString();
if ((revents_ & POLLHUP) && !(revents_ & POLLIN)){ //当事件为挂起并没有可读事件时
if (logHup_){
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_();
}
if (revents_ & POLLNVAL){ //描述字不是一个打开的文件描述符
LOG_WARN << "fd = " << fd_ << " 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;
}
当TCP连接断开,这个IO事件会触发Channel::handleEvent()
函数,而后者回调用户提供的CloseCallbask
,用户代码在onClose()
中可能可能析构Channel对象。这样就会引起Channel::handleEvent()
执行到一半时,其所属的Channel被销毁了。
为了避免core dump,需要延长某些对象的生命期,使其寿命长过Channel::handleEvent()函数。这也是muduo库TcpConnetion采用shared_ptr管理对象生命周期的原因。在TcpConnetion中建立连接后,调用Chanel::tie(TcpConnection::shared_from_this())来延长生命周期。
boost::weak_ptr<void> tie_;//保证channel所在的类生命周期延长
bool tied_;
void Channel::tie(const std::shared_ptr<void>& obj)
{
tie_ = obj;
tied_ = true;
}
void Channel::handleEvent(Timestamp receiveTime)
{
std::shared_ptr<void> guard;
if (tied_){ // 提升tie_为shared_ptr,如果提升成功,说明指向一个存在的对象
guard = tie_.lock();
if (guard) { handleEventWithGuard(receiveTime); }
}
else{ handleEventWithGuard