muduo -- Channel分析

本文详细解析了Channel类在TCP连接管理中的作用。Channel封装了fd,由TcpConnection管理和销毁,确保读写操作在单一IO线程中进行。文章深入介绍了Channel的构造、事件处理、回调机制及与TcpConnection的互动,展示了其在处理套接字事件中的核心地位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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析构:

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值