目录
Channel::handleEvent收到poller通知处理事件函数
Channel::handleEventWithGuard函数
Channel在muduo中扮演的角色
Muduo TCP Server
TCP server相当于是给muduo库提供编写服务器程序的一个入口的这么一个类。
它相当于就是一个大箱子,把这个模组库有关服务器这个编程的相关的所有的东西,包括反应堆,包括事件分发器,包括事件回调,都给它打包到一块儿了。

TCP server类代码:
TCP server成员变量:
里有event loop,有acceptor跟accept相关的操作全部打包进去,事件循环的这个线程池,有一系列的回调,再者它就有一个connection map,它维护了所有的连接。
EventLoop事件分发器 
Eventloop是最重要的事件循环,看它的这个成员变量有一个poller相当于是一个抽象基类,在poller的派生类里边实现了poll跟epoll这些lO复用的具体的一个事件分发操作,那么poller相当于就是epoll的一个抽象概念。
光有epoll不行,里边还得监听的socket。另外一个比较重要的这个成员变量表示的类型就是channel,看一看这个channel的成员变量:
包括fd、events_感兴趣的事件、revents_是具体的发生事件
现在往简单了想Eventloop就是事件循环,事件循环最重要的两个东西一个是epoll,一个就是epoll所监听的fd以及他所感兴趣的事件,以及最终发生事件以后epollwait给我们应用通知的这个事件,那么socket fd以及感兴趣的事件,通知的事件都在channel里叫做通道,epoll本身就是这个poller了。
Poller就是epoll,epoll感兴趣的fd以及它对应的事件,封装成Channel,Channel向Poller中注册,发生的事件由Poller在向Channel返回,调用预置的回调函数。
注意这里Poller返回的是活跃的Channel列表,表示这些Channel对应的文件描述符上有事件发生,当`EventLoop`从`Poller`那里得到活跃的`Channel`列表后,它会遍历这个列表并对每个`Channel`调用其`handleEvent`方法。
Channel
一个线程有一个EventLoop,一个EventLoop有一个Poller,一个Poller可以监听多个Channel,每个Channel属于一个EventLopp,一个EventLoop有很多个Channel,这就是多路(一个线程可以监听多路Channel)
Channel理解成通道,感兴趣的事件events和实际发生的事件revents都被封装在muduo库的Channel中,这些事件最终要向poller注册,发生的事件由poller给channel通知,channel得到相应fd的事件通知回调用预置的回调操作。
Channel成员变量:

EventLoop *loop_;//事件循环 const int fd_;//fd, Poller监听的对象 int events_;//注册fd感兴趣的事件 int revents_;//Poller返回的具体发生的事件 int index_; std::weak_ptr<void> tie_;//绑定自己 bool tied_;1. EventLoop 事件循环
2. fd_: poller监听的fd
3. events_ : 注册fd感兴趣的事件
4. revents_ : poller返回的具体发生的事件
这个tie是防止我们手动调用remove channel
//防止当channel被手动remove掉,channel还在执行回调操作,就是上面这些回调操作 void tie(const std::shared_ptr<void>&);成员变量:
std::weak_ptr<void> tie_;//绑定自己 bool tied_;tied_初始化设置为false,tie_设置为weak_ptr,处理事件时将其升级为shared_ptr,如果升级成功则可进行handleEventWithGuard进而执行一系列回调函数。
Channel成员函数:
Channel构造函数:

所需参数为当前loop、以及当前fd
Channel::handleEvent收到poller通知处理事件函数
Poller返回的是活跃的Channel列表,表示这些Channel对应的文件描述符上有事件发生,当`EventLoop`从`Poller`那里得到活跃的`Channel`列表后,它会遍历这个列表并对每个`Channel`调用其`handleEvent`方法。
Channel所在的fd得到poller通知后,处理事件执行的函数:

①:这里使用了一个通过强弱智能指针避免循环引用同时有效的管理和共享对象资源。
Tie方法:
Channel类在成员变量中声明:
std::weak_ptr<void> tie_;//绑定自己 bool tied_;Channel类中的方法:
void Channel::tie(const std::shared_ptr<void> &obj) { tie_ = obj; tied_ = true; }weak_ptr是shared_ptr的一种弱引用,它可以共享同一资源的访问,但并不拥有资源的所有权,weak_ptr不会增加资源引用的计数,也不会延长资源的生命周期。
bool类型的tied_,用于表示是否已经绑定了一个shared_ptr对象。
当调用tie函数将一个shared_ptr赋值给weak_ptr时,实际上是将资源的控制权交给了shared_ptr,然后将tied_标记为true,表示绑定操作已经完成。
在Channel类中建立了一个自引用关系,通过将一个shared_ptr对象传递给weak_ptr弱引用,以便在需要时可以访问或观察自身的状态或属性。
②:绑定过后,进行进一步的事件判断,首先通过调用tie_.lock()将tie_转换为一个shared_ptr对象,如果转换成功(即guard不为空)则表明与tie_绑定的对象任然存活,可以继续处理事件。如果转换失败,则说明与tie_绑定的对象已经销毁,此时可以忽略事件处理。
在处理事件之前,函数首先检查是否与外部对象关联(通过tied_标志进行检查)
如果已经关联,则尝试从tie_中获取一个shared_ptr:
- 获取成功意味着外部对象任然存活,然后安全得调用handleEventWithGuard(receiveTime);
- 获取失败意味着外部对象已经被销毁,则不调用handleEventWithGuard(receiveTime);
如果没有关联:
- 直接调用handleEventWithGuard(receiveTime);
即使这里我们最终都是调用 handleEventWithGuard(receiveTime)函数,但是通过判断tied_h和tie_状态,可以在处理事件时及进行有效的保护和判断。
Channel::handleEventWithGuard函数
poller给我们channel通知fd上发生了什么样的事,以及根据不同的事件进行相应的一些回调操作。
回调方式是用户指定的,channel来负责调用,只有channel才知道fd上到底发生了什么事件(读事件、写事件还是close事件)
fd得到poller的通知后,通知channel调用handleEvent函数进行事件处理:

给Channel设置回调函数:
使用std::function通用多态函数包装器,将类型EventCallback和ReadEventCallback定义成function对象,将函数存储和操作为对象的方式。
using EventCallback = std::function<void()>;//事件回调 using ReadEventCallback = std::function<void(Timestamp)>;//只读事件的回调//因为channel通道里面能够获知fd最终发生的具体的事件revents,所以它负责调用具体事件的回调操作 //根据不同的事件,进行相应的回调操作 //这些回调是用户设定的,通过接口传给channel来负责调用 ,channel才知道fd上是什么事件 ReadEventCallback readCallback_; EventCallback writeCallback_; EventCallback closeCallback_; EventCallback errorCallback_;对外的接口:(EventLoop、TcpConnection中会进行设置)
//设置回调函数对象 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); }这里四个set是为了提供对外的接口,设置我们的回调函数,cb是一个函数对象,存在值和地址,直接使用回导致资源复制,这里直接使用move移动语义避免不必要的复制和提高性能,将cb标记为右值引用,在调用对象的移动构造函数或移动赋值运算符完成实际移动操作。
fd处理函数:
int fd() const { return fd_; } int events() const { return events_; }//fd所感兴趣的事件 int set_revents(int revt) { return revents_ = revt; } //poller监听事件,设置了channel的fd相应事件Poller(相当于epoll)监听到了发生的事件,会通过set_revents函数来设置Channel中的revents_(set_revents是给Poller提供的接口)
Channel它对感兴趣的事件,状态信息的一个状态的描述:
//表示当前fd和其状态,是没有对任何事件感兴趣,还是对读或者写感兴趣 static const int kNoneEvent;//都不感兴趣 static const int kReadEvent;//读事件 static const int kWriteEvent;//写事件 //返回fd当前的事件状态 bool isNoneEvent() const { return events_ == kNoneEvent; } bool isWriting() const { return events_ & kWriteEvent; } bool isReading() const { return events_ & kReadEvent; }
对外提供接口,设置fd的事件类型状态:
Channel::tie()
Channel类中的`tie`成员及相关函数主要用于增强对象生命周期管理,确保`Channel`在处理回调时,相关的对象(例如`TcpConnection`)仍然存活。
在C++中,智能指针(如`std::shared_ptr`)常用于自动管理对象的生命周期。当所有的智能指针释放对象时,对象将被自动销毁。在网络编程中,特别是在异步事件驱动的框架中,确保回调时对象仍然存活是非常重要的。
1. tie()函数:
`tie()`函数是`Channel`类的一个方法,它允许你"绑定"或"关联"一个`std::weak_ptr`到`Channel`。这个`std::weak_ptr`通常指向`TcpConnection`对象。
2. tied对象的作用:
在`Channel`的某些回调执行前,会尝试从这个`std::weak_ptr`提升为`std::shared_ptr`。只有当提升成功(意味着对象仍然存活)时,回调才会执行。这确保了在回调被执行时,相关的对象(例如`TcpConnection`)仍然存在。
3. 为什么使用weak_ptr:
如果使用`std::shared_ptr`而不是`std::weak_ptr`,可能会导致对象永远不会被销毁,因为`Channel`和`TcpConnection`会相互引用,造成循环引用。而`std::weak_ptr`则不会增加引用计数,它只是一个观察者,可以尝试提升为`std::shared_ptr`,但不拥有对象。
总之,`tie`和相关机制在`muduo`中是为了确保在`Channel`的回调中,关联的`TcpConnection`对象仍然存活。这是一种有效的避免在异步事件驱动的环境中因对象提前销毁而产生的悬挂指针或无效引用的方法。
Channel::update()

Channel的内部接口,用于处理,更新poller中Channel的状态;底层是调用epoll_ctl函数。

Channel::update()内部调用的是loop中的EventLoop::updateChannel函数:

EventLoop::updateChannel调用的是EPollPoller::updateChannel函数:

实际上调用的是EPollPoller::update函数
Channel::index函数
但会channel在poller 中的位置,一个poller中有很多个channel
Channel.h
#pragma once
#include "noncopyable.h"
#include "Timestamp.h"
#include <functional>
#include <memory>
class EventLoop;
class Timestamp;
/**
* 理清楚 EventLoop、Channel、Poller之间的关系 =》 Reactor模型上对应 Demultiplex, 采用one loop per thread
* Channel 理解为通道,封装了sockfd和其感兴趣的event,如EPOLLIN、EPOLLOUT事件
* 还绑定了poller监听返回的具体事件
*/
class Channel : noncopyable
{
public:
using EventCallback = std::function<void()>;//事件回调
using ReadEventCallback = std::function<void(Timestamp)>;//只读事件的回调
Channel(EventLoop *loop, int fd);//构造函数
~Channel();//析构函数
//fd得到poller通知以后,处理事件的
//调用相应的回调方法来处理事件
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); }
//防止当channel被手动remove掉,channel还在执行回调操作,就是上面这些回调操作
void tie(const std::shared_ptr<void>&);
int fd() const { return fd_; }
int events() const { return events_; }//fd所感兴趣的事件
int set_revents(int revt) { return revents_ = revt; } //poller监听事件,设置了channel的fd相应事件
//设置fd相应的事件状态,要让fd对这个事件感兴趣
//update就是调用epoll_ctrl,通知poller把fd感兴趣的事件添加到fd上
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(); }
//返回fd当前的事件状态
bool isNoneEvent() const { return events_ == kNoneEvent; }
bool isWriting() const { return events_ & kWriteEvent; }
bool isReading() const { return events_ & kReadEvent; }
int index() { return index_; }
void set_index(int idx) { index_ = idx; }
//one loop per thread
EventLoop* ownerLoop() { return loop_; }//当前channel属于哪个eventloop
void remove();//删除channel
private://内部接口
void update();//更新,内部对象调用
void handleEventWithGuard(Timestamp receiveTime);//受保护的处理事件
//表示当前fd和其状态,是没有对任何事件感兴趣,还是对读或者写感兴趣
static const int kNoneEvent;//都不感兴趣
static const int kReadEvent;//读事件
static const int kWriteEvent;//写事件
EventLoop *loop_;//事件循环
const int fd_;//fd, Poller监听的对象
int events_;//注册fd感兴趣的事件
int revents_;//Poller返回的具体发生的事件
int index_;
std::weak_ptr<void> tie_;//绑定自己
bool tied_;
//因为channel通道里面能够获知fd最终发生的具体的事件revents,所以它负责调用具体事件的回调操作
//根据不同的事件,进行相应的回调操作
//这些回调是用户设定的,通过接口传给channel来负责调用 ,channel才知道fd上是什么事件
ReadEventCallback readCallback_;
EventCallback writeCallback_;
EventCallback closeCallback_;
EventCallback errorCallback_;
};
Channel.cc
#include "Channel.h"
#include "EventLoop.h"
#include "Logger.h"
#include <sys/epoll.h>//epoll的表示
const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI;//epoll的表示
const int Channel::kWriteEvent = EPOLLOUT;//epoll的表示
//EventLoop底层: ChannelList Poller 每个channel属于1个loop ,EventLoop *loop将Channel所属的loop记下来
Channel::Channel(EventLoop *loop, int fd)
: loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
{
}
Channel::~Channel()//析构函数
{
}
//channel的tie方法什么时候调用过?一个TcpConnection新连接创建的时候 TcpConnection => Channel
void Channel::tie(const std::shared_ptr<void> &obj)
{
tie_ = obj;
tied_ = true;
}
/**
* 当改变channel所表示fd的events事件后,update负责在poller里面更改fd相应的事件epoll_ctl
* EventLoop 包含 ChannelList Poller
*/
void Channel::update()
{
//通过channel所属的EventLoop,调用poller的相应方法,注册fd的events事件
//add code...
// loop_->updateChannel(this);
}
//在channel所属的EventLoop中, 把当前的channel删除掉
void Channel::remove()
{
//add code...
// loop_->removeChannel(this);
}
//fd得到poller通知以后,处理事件的
void Channel::handleEvent(Timestamp receiveTime)
{
if (tied_)//绑定过
{
std::shared_ptr<void> guard = tie_.lock();//弱智能指针-》强智能指针
if (guard)//是否存活
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
//根据poller通知的channel发生的具体事件, 由channel负责调用具体的回调操作
// __FUNCTION__ _LINE__ 可以打印出来具体哪个函数 哪一行
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
LOG_INFO("channel handleEvent revents:%d\n", revents_);
if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN))//发生异常了
{
if (closeCallback_)
{
closeCallback_();
}
}
if (revents_ & EPOLLERR)//错误事件
{
if (errorCallback_)
{
errorCallback_();
}
}
if (revents_ & (EPOLLIN | EPOLLPRI))
{
if (readCallback_)
{
readCallback_(receiveTime);
}
}
if (revents_ & EPOLLOUT)
{
if (writeCallback_)
{
writeCallback_();
}
}
}
文章详细介绍了Muduo网络库中的Channel类在事件驱动编程中的作用,包括它如何封装文件描述符(fd)、感兴趣的事件(events)和实际发生的事件(revents),以及如何与EventLoop和Poller交互。Channel的handleEvent方法处理由Poller通知的事件,并根据不同的事件调用预设的回调函数。此外,文章还讨论了Channel的tie()方法,用于确保在回调执行时相关对象(如TcpConnection)仍然存活。




8168

被折叠的 条评论
为什么被折叠?



