Channel 类是网络编程中事件驱动模型的核心组件之一,主要用于封装文件描述符(如套接字、定时器等)及其对应的事件和回调函数。
- Channel与Socket对象的fd是聚合关系,一个fd对应一个channel,不实际拥有fd,Channel析构不会close当前的fd。
- Channel一般作为其他类的成员,不会被直接使用,而使用更上层的封装,例如服务端的Acceptor,用于客户端的Connector,用于一次客户端服务通信的TcpConnection。最重要的是,EventLoop通过vector<Channel*>数组对注册到到其内的众多fd的管理。
- 它与 EventLoop 和 Poller 紧密协作,实现了事件的注册、监听和处理。具体作用如下:
1. 事件管理:Channel 类负责管理文件描述符感兴趣的事件(如可读、可写、错误等),并提供了一系列方法来注册和注销这些事件。
2. 事件回调:通过设置不同的回调函数(如读回调、写回调、关闭回调、错误回调),Channel 类可以在相应事件发生时执行特定的操作。
3. 多线程安全:通过 tie 方法,Channel 类可以捆绑一个共享指针,用于在多线程环境中监听对象的生存情况,确保在对象销毁时不会发生错误。
我们知道,当客户端正常断开TCP连接,IO事件会触发Channel中的设置的`CloseCallback`回调,但是用户代码在`onClose()`中有可能析构Channel对象,导致回调执行到一半的时候,其所属的Channel对象本身被销毁了。这时程序会出现问题。muduo的解决办法是提供`Channel::tie(const boost::shared_ptr<void>&)`这个函数,用于延长某些对象的生命期,使之长过`Channel::handleEvent()`函数。所以muduo库中的 `TcpConnection`采用了shared_ptr管理对象生命期。
成员变量
private:
EventLoop *loop_; // 当前Channel 关联的 EventLoop
const int fd_; // Poller监听的文件描述符
int events_; // 注册fd感兴趣的事件.写入epoll的事件
int revents_; // poller返回的具体发生的事件,从epoll读取的事件
int index_; // 在Poller上注册的情况: KNew kAdded kDeleted
//用于多线程
std::weak_ptr<void> tie_; // 多线程中监听对象的生存情况
bool tied_; // 捆绑
bool eventHandling_; // 是否处于事件循环中(正处理事件)
bool addedToLoop_; // 是否已被添加到事件循环
// 处理事件的回调函数
ReadEventCallback readCallback_; //lfd cfd
EventCallback writeCallback_; //cfd
EventCallback closeCallback_; //cfd
EventCallback errorCallback_; //cf
public:
// 事件回调函数定义
using EventCallback = std::function<void()>;
// 读事件回调函数定义
using ReadEventCallback = std::function<void(Timestamp)>;
常量定义
static const int kNew = -1; // Channel 要添加到map和epoll红黑树中
static const int kAdded = 1; // Channel 在map 和 epoll 的红黑树中
static const int kDeleted = 2; // Channel 在map中,但是不在epoll 的红黑树中
重要方法
void handleEvent(Timestamp receiveTime)
{
std::shared_ptr<void> guard; // 创建空的强引用
if (tied_) // 若已绑定TcpConnection对象
{
guard = tie_.lock(); // 尝试将弱引用提升为强引用
if (guard) // 若提升成功,说明对象仍存活
{
handleEventWithGuard(receiveTime); // 安全处理事件
}
// 若提升失败(guard为空),则不处理事件,直接返回
}
else // 若未绑定TcpConnection对象(如独立Channel)
{
handleEventWithGuard(receiveTime); // 直接处理事件
}
}
在这里,我们可以看出`handleEvent`中tie实际上这是一个弱指针,绑定到`TcpConnection`的共享指针 ,如果可以原来的弱指针,变成了强指针,这时候tie()的作用就表明了,延长了`TcpConnection`的生命周期,使之长过`Channel::handleEvent()`,保证了`TcpConnection`不被销毁,因此Channel中存了一个`TcpConnection`的弱指针,在处理事件的时候,lock将引用计数加1,`Channel::handleEvent()`来关闭连接时,guard变量依然持有一份`TcpConnection`。也就是说Channel不会在执行完`Channel::handleEvent()`之前被析构。
//捆绑一个共享指针,用于多线程中监听对象的生存情况
//obj 要捆绑的共享指针
void tie(const std::shared_ptr<void> &obj)
{
tie_ = obj;
tied_ = true;
}