1. 模块概述
1.1 设计目标
Channel 是 mymuduo 网络库中的事件分发器,它封装了文件描述符(fd)及其感兴趣的事件和事件回调。Channel 的核心设计目标包括:
- 事件抽象:将文件描述符和事件回调统一封装
- 回调管理:为不同类型的事件(读、写、错误、关闭)提供回调机制
- 生命周期管理:通过 tie 机制防止对象被提前销毁
- 事件状态跟踪:记录当前监听的事件和在 Poller 中的状态
1.2 Channel 的作用
Channel 为 事件(文件描述符)与回调的连接器。
- 一对一关系 :在网络编程中,每一个 socket 连接(或者 timerfd 等)都有一个唯一的文件描述符 fd。一个 Channel 对象专门负责管理一个 fd。
- 承上启下 :
- 向下:它告诉底层的 Poller(如 epoll):"我关注这个 fd 的读/写事件"。
- 向上 :当 Poller 发现事件发生时,通知 EventLoop,EventLoop 找到对应的 Channel,Channel 再调用预先设置好的 回调函数(比如 onMessage, onConnection)。
- 不拥有 fd:Channel 只是管理 fd 的状态,它不负责创建或关闭 fd(通常由 TcpConnection 或 Socket 类负责),它只负责"监听"和"通知"。
2. 源码
2.1 Channel.h
cpp
#pragma once
#include "NonCopyable.h"
#include "Timestamp.h"
#include <functional> // std::function 回调
#include <memory> // 智能指针
namespace mymuduo {
class EventLoop; // 前向声明,避免循环依赖
/**
* Channel 类:封装一个fd、在Poller中的状态标识、所属的EventLoop、各种事件回调
* 关键成员:
* int fd_; // 管理的文件描述符
* int events_; // 关注的事件类型(EPOLLIN等)
* int revents_; // 实际发生的事件类型
* int index_; // 在Poller中的状态标识
* EventLoop* loop_; // 所属的EventLoop
* ..... // 各种事件回调
*
*/
class Channel : NonCopyable {
public:
// 定义回调类型
using EventCallback = std::function<void()>; // 通用事件回调(无参数)
using ReadEventCallback = std::function<void(Timestamp)>; // 读事件回调(带时间戳)
Channel(EventLoop *loop, int fd); // 构造函数(需传入所属 EventLoop 和 fd)
~Channel() = default; // 默认析构
// 核心方法:处理事件(由 EventLoop 调用)
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 被提前销毁)
void tie(const std::shared_ptr<void> &);
// 获取 fd 和事件状态
int fd() const { return fd_; } // 返回管理的文件描述符
int events() const { return events_; } // 返回当前监听的事件
void set_revents(int revt) { revents_ = revt; } // 提供给poller的接口,poller检测到了实际发生的事件,通知给channel
// 事件监听控制
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 isNoneEvent() const { return events_ == kNoneEvent; } // 是否无监听事件
bool isWriting() const { return events_ & kWriteEvent; } // 是否监听写事件
bool isReading() const { return events_ & kReadEvent; } // 是否监听读事件
// 用于 Poller 管理的索引(标识 Channel 在 Poller 中的位置)
int index() { return index_; }
void set_index(int idx) { index_ = idx; }
// 获取所属 EventLoop
EventLoop *ownerLoop() { return loop_; }
// 从 EventLoop 中移除自己
void remove();
private:
// 内部方法
void update(); // 更新事件监听状态(通知 EventLoop 调用 Poller)
void handleEventWithGuard(Timestamp receiveTime); // 带生命周期保护的事件处理
// 静态常量(事件类型标志)
static const int kNoneEvent; // 无事件
static const int kReadEvent; // 读事件(EPOLLIN)
static const int kWriteEvent; // 写事件(EPOLLOUT)
// 成员变量
EventLoop *loop_; // 所属的事件循环(一个 Channel 只属于一个 EventLoop)
const int fd_; // 管理的文件描述符(如 socket、timerfd)
int events_; // 当前注册的事件(用户设置)
int revents_; // Poller 返回的就绪事件(由 EventLoop 设置)
int index_; // 在 Poller 中的索引(方便快速查找)
// 生命周期管理
std::weak_ptr<void> tie_; // 弱引用绑定(如 shared_ptr<TcpConnection>)
bool tied_; // 是否已绑定对象
// 回调函数
ReadEventCallback readCallback_; // 读事件回调
EventCallback writeCallback_; // 写事件回调
EventCallback closeCallback_; // 关闭事件回调
EventCallback errorCallback_; // 错误事件回调
}; // Channel 类
} // namespace mymuduo
2.2 Channel.cpp
cpp
#include "LogStream.h"
#include "Channel.h"
#include "EventLoop.h"
#include <sys/epoll.h>
using namespace mymuduo;
const int Channel::kNoneEvent = 0; //空事件
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI; //读事件, EPOLLPRI:紧急(带外)数据可读
const int Channel::kWriteEvent = EPOLLOUT; //写事件
Channel::Channel(EventLoop *loop, int fd)
: loop_(loop)
, fd_(fd)
, events_(0)
, revents_(0)
, index_(-1)
, tied_(false){}
void Channel::tie(const std::shared_ptr<void> &obj){ // TcpConnection* 转为 void*
tie_ = obj;
tied_ = true; // 标记channel是否绑定了TcpConnection对象, weak_ptr 是否绑定到了TcpConnection对象
}
void Channel::update(){
// 通过channel所属的eventloop,调用poller的相应方法,更改poller中fd的事件
loop_->updateChannel(this);
}
void Channel::remove(){
loop_->removeChannel(this);
}
void Channel::handleEvent(Timestamp receiveTime){
if (tied_){ // 如果channel绑定了TcpConnection对象
std::shared_ptr<void> guard = tie_.lock(); // 提升为shared_ptr, 检查TcpConnection对象是否存在
if (guard){ // 如果提升成功了, 说明TcpConnection对象还存在
handleEventWithGuard(receiveTime); // 处理事件
}
// 绑定了TcpConnection对象, 但是提升失败了 就不做任何处理 说明Channel的TcpConnection对象已经不存在了
}else{
handleEventWithGuard(receiveTime); // 没绑定,直接处理事件
}
}
void Channel::handleEventWithGuard(Timestamp receiveTime){
LOG_INFO << "channel handleEvent revents: " << revents_;
// 关闭,发生了 EPOLLHUP(对端关闭连接)且没有数据可读(!EPOLLIN)
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_();
}
}
3. 模块详解
3.1 events_ vs revents_
- events_:监听的事件。比如调用 enableReading() 后,events_ 会加上读标志。
- revents_:实际发生的事件。epoll_wait 返回后,内核告诉 EventLoop 哪个 fd 发生了什么事,EventLoop 把这个结果填到 revents_ 里,然后让 Channel 处理。
3.2 事件控制:委托模式
cpp
void enableReading() { events_ |= kReadEvent; update(); }
void disableReading() { events_ &= ~kReadEvent; update(); }
void Channel::update(){
loop_->updateChannel(this);
}
void Channel::remove(){
loop_->removeChannel(this);
}
- 用来修改 events_(要监听的事件)。
- 修改完后,立刻调用 update()。update() 的作用是通知 EventLoop:"我要监听的事件变了,你去告诉 Poller 更新一下内核里的监听列表"。
- Channel 自己不能直接操作 epoll_ctl。它通过 loop_(所属的事件循环),让 EventLoop 去调用 Poller 的 updateChannel 或 removeChannel。
- 典型的委托模式。Channel 只负责记录状态,Poller 负责系统调用。
3.3 生命周期管理 (tie)
防止 Channel 还在处理事件,但拥有它的对象(如 TcpConnection)已经被销毁了。
cpp
std::weak_ptr<void> tie_;
bool tied_;
void tie(const std::shared_ptr<void> &);
void Channel::tie(const std::shared_ptr<void> &obj){ // TcpConnection* 转为 void*
tie_ = obj;
tied_ = true; // 标记channel是否绑定了TcpConnection对象, weak_ptr 是否绑定到了TcpConnection对象
}
void Channel::handleEvent(Timestamp receiveTime){
if (tied_){ // 如果channel绑定了TcpConnection对象
std::shared_ptr<void> guard = tie_.lock(); // 提升为shared_ptr, 检查TcpConnection对象是否存在
if (guard){ // 如果提升成功了, 说明TcpConnection对象还存在
handleEventWithGuard(receiveTime); // 处理事件
}
// 绑定了TcpConnection对象, 但是提升失败了 就不做任何处理 说明Channel的TcpConnection对象已经不存在了
}else{
handleEventWithGuard(receiveTime); // 没绑定,直接处理事件
}
}
- 场景:Channel 是被 TcpConnection 拥有的。TcpConnection 是一个 shared_ptr 管理的对象。
- 作用:把 TcpConnection 的 shared_ptr 存到 Channel 的 weak_ptr tie_ 里,当 Channel 要处理事件时,先试着把 weak_ptr 提升为 shared_ptr。如果提升成功,说明 TcpConnection 还活着,可以安全调用回调;如果失败,说明 TcpConnection 已经没了,就别处理了,防止崩溃。
tie 作用流程详解:
- 正常情况 :
- TcpConnection (对象 A) 拥有一个 Channel。
- TcpConnection 调用 channel->tie(shared_from_this())。
- Channel 内部 tie_ 持有了 A 的弱引用。
- 事件来了 -> handleEvent -> tie_.lock() 成功拿到 A 的强引用 -> 调用回调 -> 安全。
- 危险情况(没有 tie) :
- 主线程决定关闭连接,销毁了 TcpConnection (对象 A)。
- 但是 IO 线程的 epoll_wait 刚好返回,通知这个 fd 有事件。
- Channel 还在,它尝试调用回调,回调里访问 this->connection_->xxx(因为当 Channel 触发 readCallback_ 时,实际上执行的是 TcpConnection::handleRead。)。
- 错误,connection_ 已经成为野指针。
- 安全情况(有 tie) :
- 主线程销毁 TcpConnection (对象 A)。此 时引用计数归 0,A 被析构。
- Channel 里的 weak_ptr tie_ 自动失效(变成 expired)。
- IO 线程 epoll_wait 返回,通知事件。
- Channel::handleEvent 执行 tie_.lock()。
- 因为 A 已死,lock() 返回空 shared_ptr。
- if (guard) 判断为假。
- 函数直接返回,不执行回调。安全!
3.4 事件分发
cpp
void Channel::handleEventWithGuard(Timestamp receiveTime){
LOG_INFO << "channel handleEvent revents: " << revents_;
// 1. 关闭事件 (EPOLLHUP 且没有数据可读)
if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)){
if (closeCallback_) closeCallback_();
}
// 2. 错误事件
if (revents_ & EPOLLERR){
if (errorCallback_) errorCallback_();
}
// 3. 读事件
if (revents_ & (EPOLLIN | EPOLLPRI)){
if (readCallback_) readCallback_(receiveTime);
}
// 4. 写事件
if (revents_ & EPOLLOUT){
if (writeCallback_) writeCallback_();
}
}
- 根据 revents_(内核告诉我们实际发生的事件),去调用对应的回调函数。
- 注意:一个 fd 可能同时满足多个条件(比如既可读又可写),所以这里用的是多个 if 而不是 if-else if。