Channel类
- channel封装fd(也就是socket),从创建至销毁只属于一个io eventloop线程
- channel由TcpConnection管理和销毁
- channel对象读写、更新都在一个io线程,读写不需要加锁
Channel的作用和成员变量
1. Channel fd 封装类,封装套接字、timefd等fd。
2. Channel类一般不单独使用,它常常包含在其他类中(Acceptor、Connector、EventLoop、TimerQueue、TcpConnection)使用。Channel对象生存期由这些类控制。
3. Channel类有EventLoop的指针 loop_,通过这个指针可以向EventLoop中添加当前Channel事件。
void Channel::update() {
addedToLoop_ = true;
loop_->updateChannel(this);
}
4. 事件类型用events_表示,不同事件类型对应不同回调函数。
void Channel::handleEventWithGuard(Timestamp receiveTime) {
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))// 判断返回的事件 为挂断 close
if (closeCallback_) closeCallback_();
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))// 对等放调用close关闭连接 会受到POLLRDHUP POLLPRI(带外数据)
if (readCallback_) readCallback_(receiveTime);// 产生可读事件 调用读函数
if (revents_ & POLLOUT)
if (writeCallback_) writeCallback_();// 可写事件的产生 调用写的回调函数
}
5. Channel析构时:Channel::~Channel()->EventLoop::removeChannel(Channel*)->Poller::removeChannel(Channel*)将Poller中的Channel*移除防止空悬指针。这是因为Channel的生命周期和Poller/EventLoop不一样长。
boost::weak_ptr<void> tie_;// 这是一个弱引用 用于对象生命期的控制 TcpConnection
bool tied_;
bool eventHandling_;
bool addedToLoop_;
ReadEventCallback readCallback_;
EventCallback writeCallback_;
EventCallback closeCallback_;
EventCallback errorCallback_;
eventloop 收到套接字或者事件可读写事件后,会触发handleEvent
handleEvent是channel的核心,由EventLoop::loop()通过channel对象调用,然后该函数根据revents_的值分别调用不同的用户回调。
void Channel::handleEvent(Timestamp receiveTime) {
boost::shared_ptr<void> guard;
if (tied_)
{
//lock从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源
//handleEventWithGuard会进行recv处理,POLLIN可读就调用Channel之前注册的读回调函数,此外还有写回调,关闭回调等,这些都是TcpServer注册到TcpConnection在关联到TcpConnection所拥有的已连接套接字的,通过执行newConnection()函数之时,你可以回头看看。
//可见,处理连接事件时,对TcpConnection的智能指针进行了提升,因为已连接套接字fd的生命期是由TcpConnection管理的,我们要确保处理事件时该对象还存活,否则使用一个已经死亡的对象,结果只有core dump。
guard = tie_.lock();// 这里是对弱指针的一个提升
if (guard)
{
LOG_INFO << "tie_ handleEvent";
handleEventWithGuard(receiveTime);// 调用提前注册的回调函数处理读写事件
}
}
else
{
// LOG_INFO << "no tie_ handleEvent";
handleEventWithGuard(receiveTime);
}
}
根据revents_的值(目前活动的事件)分别调用不同的用户回调,也就是说channel对象处理fd上各种类型的事件,与events_无关。这里利用Channel对象的“多态性”。如果是客户端socket,可读事件就会调用预先设置的回调函数;如果是listen socket,则调用Aceptor对象的handleRead()。
那么revents_是什么时候被赋值?
1. EventLoop::loop ----> EPollPoller::poll ----> epoll_wait ----> EPollPoller::fillActiveChannels
channel->set_revents(events_[i].events); //把已发生的事件传给channel,写到通道当中
activeChannels->push_back(channel);
2. 下面代码 是就绪事件列表(activeChannels_)填充、处理过程。
// 部分无关代码被我去掉
void EventLoop::loop() {
while ( !quit_ ) {
// 每次poll调用,就是一次重新填充activeChannels_的过程 所以这里需要清空
activeChannels_.clear();
// 调用poll获得当前活动事件的channel列表(其实是将有活动事件的fd对应的channel填入activechannels_),然后依次调用每个channel的handleEvent函数
// 这一步的实质是进行poll或者epoll_wait调用
// 根据fd的返回事件,填充对应的Channel,以准备后面执行回调处理事件
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
eventHandling_ = true;
for (ChannelList::iterator it = activeChannels_.begin(); it != activeChannels_.end(); ++it) {
currentActiveChannel_ = *it;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL;
eventHandling_ = false;
// 执行等待队列中的回调函数
doPendingFunctors();
}
Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels) {
int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
fillActiveChannels(numEvents, activeChannels); //调用fillActiveChannels,传入numEvents也就是发生的事件数目
}
void EPollPoller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const {
channel->set_revents(events_[i].events); //把已发生的事件传给channel,写到通道当中
activeChannels->push_back(channel);
}
Channel构造:
在TcpConnection构造函数内,使用TcpConnection的handleRead handleWrite handleClose handleError给 Channel 设置各种回调函数。
简单说一下Channel和TcpConnection的关系:
- 每个 对象中有一个成员变量 channel_(new Channel(loop, sockfd)),
- 每个Channel对象中有一个成员变量 boost::weak_ptr<void> tie_;// 这是一个弱引用 指向他的拥有者TcpConnection一个连接对象
/*
* 1. 当连接到来,创建一个TcpConnection对象,立刻用shared_ptr来管理,引用计数为1,
* 在Channel中维护一个weak_ptr(tie_),将这个shared_ptr对象赋值给_tie,引用计数仍然为1。
* 2. 当连接关闭时,在handleEvent中,将tie_提升,得到一个shard_ptr对象,引用计数就变成了2。
* 当shared_ptr的计数不为0时,TcpConnection不会被销毁。
*/
void Channel::tie(const boost::shared_ptr<void>& obj)
{
tie_ = obj;
tied_ = true;
}
tie_是TcpConnection的弱引用,在调用TcpConnection的函数之前判断它是否还存在,如果被析构了,那么提升的shared_ptr会是null
每个TcpConnection对象代表一个tcp连接,所以TcpConnection中需要保存用于服务器/客户端通信的套接字,这个套接字就记录在Channel中
- TcpConnection在创建之初会为Channel设置回调函数,如果套接字可读/可写/错误/关闭等就会执行TcpConnection中的函数
- TcpConnection在确定连接已经建立后会向Poller注册自己的Channel
TcpConnection::TcpConnection(EventLoop* loop,
const string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
{
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this, _1));
//如果tcp缓冲区不足以全部容纳数据,就会开启对可写事件的监听,当tcp缓冲区可写,就调用Channel的回调函数,这个回调函数也是在TcpConnection构造函数中传给Channel的
channel_->setWriteCallback(
boost::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(
boost::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(
boost::bind(&TcpConnection::handleError, this));
LOG_INFO << "TcpConnection::ctor[" << name_ << "] at " << this << " fd=" << sockfd;
socket_->setKeepAlive(true);
}
void Channel::handleEventWithGuard(Timestamp receiveTime)中回调,在TcpConnection构造函数中设置的回调。
Channel析构: