目录
[一、 Channel.h](#一、 Channel.h)
[1. Channel 的整体定位与核心设计目标](#1. Channel 的整体定位与核心设计目标)
[2. 核心常量与成员变量解析](#2. 核心常量与成员变量解析)
[3. 核心接口逐模块拆解](#3. 核心接口逐模块拆解)
[1. 构造 / 析构:关联 EventLoop 与 fd](#1. 构造 / 析构:关联 EventLoop 与 fd)
[2. 事件回调设置:支持移动语义](#2. 事件回调设置:支持移动语义)
[3. tie 机制:防止回调悬空(核心安全机制)](#3. tie 机制:防止回调悬空(核心安全机制))
[4. 事件注册 / 注销:修改关注的事件](#4. 事件注册 / 注销:修改关注的事件)
[5. 核心事件处理:handleEvent](#5. 核心事件处理:handleEvent)
[6. 辅助接口:供 Poller / 上层使用](#6. 辅助接口:供 Poller / 上层使用)
[4. 关键机制深度解析](#4. 关键机制深度解析)
[1. tie 机制的核心价值(防回调悬空)](#1. tie 机制的核心价值(防回调悬空))
[2. update () 的核心流程](#2. update () 的核心流程)
[3. Channel 不持有 fd 的设计哲学](#3. Channel 不持有 fd 的设计哲学)
[5. 设计亮点总结](#5. 设计亮点总结)
[二、 Channel.cc](#二、 Channel.cc)
[1. 代码整体核心逻辑回顾](#1. 代码整体核心逻辑回顾)
[2. 逐函数拆解核心实现](#2. 逐函数拆解核心实现)
[1. 态常量初始化](#1. 态常量初始化)
[2. 构造函数:初始化状态 + 绑定依赖](#2. 构造函数:初始化状态 + 绑定依赖)
[3. 析构函数:严格的安全校验](#3. 析构函数:严格的安全校验)
[4. tie 机制:绑定上层对象(防回调悬空)](#4. tie 机制:绑定上层对象(防回调悬空))
[5. update/remove:与 EventLoop 交互](#5. update/remove:与 EventLoop 交互)
[6. handleEvent:事件处理入口(核心)](#6. handleEvent:事件处理入口(核心))
[7. handleEventWithGuard:事件分发核心(优先级 + 回调)](#7. handleEventWithGuard:事件分发核心(优先级 + 回调))
[8. 调试辅助:事件转字符串](#8. 调试辅助:事件转字符串)
[3. 核心设计亮点总结](#3. 核心设计亮点总结)
一、 Channel.h
先贴出完整代码,再逐部分解释:
cpp
// Copyright 2010, Shuo Chen. All rights reserved.
// http://code.google.com/p/muduo/
//
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
// 作者:陈硕 (chenshuo at chenshuo dot com)
//
// 这是一个内部头文件,你不应该直接包含它。
#ifndef MUDUO_NET_CHANNEL_H
#define MUDUO_NET_CHANNEL_H
#include "muduo/base/noncopyable.h" // 不可拷贝基类
#include "muduo/base/Timestamp.h" // 时间戳类(记录事件触发时间)
#include <functional> // 函数对象(事件回调)
#include <memory> // 智能指针(tie 机制)
namespace muduo
{
namespace net
{
class EventLoop; // 前向声明:Channel 所属的事件循环
///
/// 可被选择的 I/O 通道(封装文件描述符的事件管理)
///
/// 该类**不拥有**文件描述符(fd)的生命周期,仅负责管理其 IO 事件;
/// 文件描述符可以是 socket、eventfd、timerfd 或 signalfd 等支持 IO 多路复用的 fd
class Channel : noncopyable
{
public:
// 通用事件回调类型(无参数、无返回值)
typedef std::function<void()> EventCallback;
// 读事件回调类型(携带事件触发的时间戳)
typedef std::function<void(Timestamp)> ReadEventCallback;
/// 构造函数:创建 Channel 对象
/// @param loop 该 Channel 所属的 EventLoop(一个 Channel 只能属于一个 EventLoop)
/// @param fd 要管理的文件描述符(如 socket fd)
Channel(EventLoop* loop, int fd);
~Channel();
/// 处理已触发的事件(由 EventLoop 调用)
/// @param receiveTime 事件被 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 绑定到一个由 shared_ptr 管理的宿主对象
/// 核心目的:防止在 handleEvent 执行过程中,宿主对象被销毁(悬空指针)
/// @param obj 宿主对象的 shared_ptr(void 类型兼容任意类型)
void tie(const std::shared_ptr<void>&);
/// 获取管理的文件描述符
int fd() const { return fd_; }
/// 获取当前关注的事件类型(由用户设置,如读/写事件)
int events() const { return events_; }
/// 设置实际触发的事件类型(由 Poller 调用,如 epoll 返回的 revents)
void set_revents(int revt) { revents_ = revt; } // 供 Poller 内部使用
/// 检查是否当前无关注的事件
bool isNoneEvent() const { return events_ == kNoneEvent; }
/// 启用读事件:将读事件加入关注列表,更新到 Poller
void enableReading() { events_ |= kReadEvent; update(); }
/// 禁用读事件:从关注列表移除读事件,更新到 Poller
void disableReading() { events_ &= ~kReadEvent; update(); }
/// 启用写事件:将写事件加入关注列表,更新到 Poller
void enableWriting() { events_ |= kWriteEvent; update(); }
/// 禁用写事件:从关注列表移除写事件,更新到 Poller
void disableWriting() { events_ &= ~kWriteEvent; update(); }
/// 禁用所有事件:清空关注列表,更新到 Poller
void disableAll() { events_ = kNoneEvent; update(); }
/// 检查是否正在关注写事件
bool isWriting() const { return events_ & kWriteEvent; }
/// 检查是否正在关注读事件
bool isReading() const { return events_ & kReadEvent; }
// 供 Poller 使用:标记 Channel 在 Poller 内部数据结构中的索引(如 pollfd 数组下标)
int index() { return index_; }
void set_index(int idx) { index_ = idx; }
// 调试用:将实际触发的事件(revents)转换为可读字符串(如 "READ | CLOSE")
string reventsToString() const;
// 调试用:将关注的事件(events)转换为可读字符串
string eventsToString() const;
/// 禁止记录 HUP 事件日志(用于特殊场景,如忽略连接断开的日志)
void doNotLogHup() { logHup_ = false; }
/// 获取当前 Channel 所属的 EventLoop
EventLoop* ownerLoop() { return loop_; }
/// 将当前 Channel 从所属 EventLoop 的 Poller 中移除
void remove();
private:
/// 静态辅助函数:将 fd 和事件类型转换为可读字符串(调试用)
static string eventsToString(int fd, int ev);
/// 更新当前 Channel 的事件关注状态到 Poller(由 EventLoop 转发给 Poller)
void update();
/// 带 guard 的事件处理(保证 handleEvent 执行期间宿主对象不被销毁)
/// @param receiveTime 事件触发时间戳
void handleEventWithGuard(Timestamp receiveTime);
// 事件类型常量定义
static const int kNoneEvent; // 无事件(默认值)
static const int kReadEvent; // 读事件(包含 EPOLLIN/EPOLLPRI 等)
static const int kWriteEvent; // 写事件(EPOLLOUT)
EventLoop* loop_; // 所属的 EventLoop(一个 Channel 绑定一个 EventLoop)
const int fd_; // 管理的文件描述符(const:Channel 生命周期内 fd 不变)
int events_; // 用户关注的事件类型(如 EPOLLIN | EPOLLOUT)
int revents_; // Poller 检测到的实际触发的事件类型(epoll/poll 返回值)
int index_; // Poller 内部使用的索引(优化查找/更新)
bool logHup_; // 是否记录 HUP 事件日志(默认 true)
std::weak_ptr<void> tie_; // 弱引用:绑定到宿主对象(避免循环引用)
bool tied_; // 是否已绑定宿主对象
bool eventHandling_; // 是否正在处理事件(防止 reentrant 问题)
bool addedToLoop_; // 是否已添加到 EventLoop 的 Poller 中
ReadEventCallback readCallback_; // 读事件回调
EventCallback writeCallback_; // 写事件回调
EventCallback closeCallback_; // 关闭事件回调
EventCallback errorCallback_; // 错误事件回调
};
} // namespace net
} // namespace muduo
#endif // MUDUO_NET_CHANNEL_H
1. Channel 的整体定位与核心设计目标
Channel 是 Muduo Reactor 模型的核心事件处理器,也是 EventLoop(事件循环)和 Poller(IO 多路复用器,如 epoll/poll)之间的桥梁,核心设计目标:
- fd 与事件解耦:每个 fd(socket/eventfd/timerfd/signalfd)对应一个 Channel,Channel 封装该 fd 关注的事件(events_)和实际发生的事件(revents_);
- 事件回调管理:为不同 IO 事件(读 / 写 / 关闭 / 错误)设置独立回调函数,事件发生时自动分发;
- 不持有 fd:Channel 仅 "管理" fd 的事件,不负责 fd 的创建 / 关闭,fd 的生命周期由上层(如 TcpConnection、TimerQueue)管理;
- 线程安全保障:每个 Channel 只属于一个 EventLoop(单线程),避免多线程竞争;
- 防回调悬空:通过 tie 机制绑定上层对象的 shared_ptr,防止回调执行时对象已析构。
在 Reactor 模型中,Channel 的核心角色:
cpp
EventLoop(事件循环) → 管理多个 Channel → Poller(监听 Channel 的 fd 事件)→ 事件发生时调用 Channel::handleEvent → 执行对应回调
2. 核心常量与成员变量解析
cpp
// 核心事件常量(封装epoll/poll的事件类型,简化上层使用)
static const int kNoneEvent = 0; // 无关注事件
static const int kReadEvent = POLLIN | POLLPRI; // 读事件(普通读 + 紧急数据)
static const int kWriteEvent = POLLOUT; // 写事件
// 核心成员变量
EventLoop* loop_; // Channel所属的EventLoop(一个Channel仅属于一个EventLoop)
const int fd_; // 封装的fd(const,创建后不可修改)
int events_; // 关注的事件(如kReadEvent/kWriteEvent),由上层设置
int revents_; // Poller返回的实际发生的事件(仅Poller修改)
int index_; // Poller中用于标识Channel的索引(如epoll的红黑树节点索引)
bool logHup_; // 是否记录POLLHUP事件(默认true,用于调试/日志)
// tie机制:防止回调时上层对象已析构
std::weak_ptr<void> tie_; // 弱引用,观测上层对象的shared_ptr
bool tied_; // 是否已绑定tie
bool eventHandling_; // 是否正在处理事件(防止处理中移除Channel)
bool addedToLoop_; // 是否已添加到EventLoop的Poller中
// 事件回调函数(可移动,减少拷贝)
ReadEventCallback readCallback_; // 读事件回调(带时间戳,记录事件发生时间)
EventCallback writeCallback_; // 写事件回调
EventCallback closeCallback_; // 关闭事件回调
EventCallback errorCallback_; // 错误事件回调
关键设计点:
fd_是 const:一个 Channel 始终绑定一个 fd,避免中途修改导致事件管理混乱;loop_唯一:Channel 的所有操作(如 enableReading、handleEvent)必须在所属 EventLoop 的线程中执行;revents_仅由 Poller 修改:Poller 监听事件后,设置 revents_,再调用 Channel::handleEvent;tie_是 weak_ptr:不影响上层对象的生命周期,仅在回调时检查对象是否存在。
3. 核心接口逐模块拆解
1. 构造 / 析构:关联 EventLoop 与 fd
cpp
// 构造函数:绑定EventLoop和fd,初始化默认状态
Channel(EventLoop* loop, int fd);
// 析构函数:不关闭fd,仅确保Channel从EventLoop中移除
~Channel();
核心规则:
- 每个 Channel 必须关联一个 EventLoop,且只能在该 EventLoop 的线程中操作;
- 析构时不会调用
close(fd_),fd 的关闭由上层(如 TcpConnection::connectDestroyed)负责。
2. 事件回调设置:支持移动语义
cpp
// 读事件回调:带Timestamp(事件发生时间,用于日志/超时判断)
typedef std::function<void(Timestamp)> ReadEventCallback;
// 其他事件回调:无参数
typedef std::function<void()> EventCallback;
// 设置回调:用std::move减少拷贝(std::function支持移动)
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); }
设计亮点:
- 读回调带 Timestamp:记录事件发生的精确时间,方便上层做超时判断(如 TCP 连接超时)、日志记录;
- 回调函数用 std::function:支持任意可调用对象(lambda、bind、成员函数),灵活适配不同场景。
3. tie 机制:防止回调悬空(核心安全机制)
cpp
// 绑定上层对象的shared_ptr(如TcpConnection的shared_ptr)
void tie(const std::shared_ptr<void>&);
核心原理:
- 上层调用
tie(shared_ptr<T>),Channel 保存该指针的 weak_ptr(tie_); - 当 Poller 触发
handleEvent时,Channel 先通过tie_.lock()获取 shared_ptr:- 若 lock 成功(对象存在):执行回调;
- 若 lock 失败(对象已析构):不执行回调,避免野指针访问。
tied_标记是否已绑定,未绑定则直接执行回调(无开销)。
4. 事件注册 / 注销:修改关注的事件
cpp
// 启用读事件:修改events_,并更新到Poller
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 isWriting() const { return events_ & kWriteEvent; }
bool isReading() const { return events_ & kReadEvent; }
bool isNoneEvent() const { return events_ == kNoneEvent; }
核心逻辑:
- 所有事件修改操作都调用
update():update()会调用loop_->updateChannel(this),进而调用 Poller::updateChannel,将修改后的 events_ 注册到 epoll/poll 中; - 事件操作是幂等的:多次调用 enableReading 不会重复注册,仅修改 events_ 后更新。
5. 核心事件处理:handleEvent
cpp
// 处理Poller返回的事件(核心入口)
void handleEvent(Timestamp receiveTime);
// 内部函数:带tie保护的事件处理(防止回调时对象析构)
void handleEventWithGuard(Timestamp receiveTime);
核心流程:
handleEvent先检查 tie 状态:- 若已 tie:lock 获取 shared_ptr,成功则调用
handleEventWithGuard,失败则直接返回; - 若未 tie:直接调用
handleEventWithGuard;
- 若已 tie:lock 获取 shared_ptr,成功则调用
handleEventWithGuard根据 revents_ 分发事件:- 处理关闭事件(POLLRDHUP)→ 调用 closeCallback_;
- 处理错误事件(POLLERR)→ 调用 errorCallback_;
- 处理读事件(POLLIN/POLLPRI)→ 调用 readCallback_(带时间戳);
- 处理写事件(POLLOUT)→ 调用 writeCallback_;
- 处理挂起事件(POLLHUP)→ 日志记录(若 logHup_ 为 true)。
6. 辅助接口:供 Poller / 上层使用
cpp
// 获取/设置fd、事件、索引(Poller专用)
int fd() const { return fd_; }
int events() const { return events_; }
void set_revents(int revt) { revents_ = revt; } // 仅Poller调用
int index() { return index_; }
void set_index(int idx) { index_ = idx; }
// 调试用:将事件转换为字符串(如"POLLIN POLLOUT")
string reventsToString() const;
string eventsToString() const;
// 控制日志:是否记录HUP事件
void doNotLogHup() { logHup_ = false; }
// 获取所属的EventLoop
EventLoop* ownerLoop() { return loop_; }
// 从EventLoop中移除Channel(调用loop_->removeChannel(this))
void remove();
4. 关键机制深度解析
1. tie 机制的核心价值(防回调悬空)
问题场景:上层对象(如 TcpConnection)持有 Channel,若在 Poller 触发事件、执行 Channel 回调的过程中,TcpConnection 被析构,回调会访问已析构的对象(野指针)。
解决方案:
cpp
// 上层调用(如TcpConnection)
channel_->tie(shared_from_this());
// Channel::handleEvent中
if (tied_) {
std::shared_ptr<void> guard = tie_.lock(); // 尝试获取shared_ptr
if (guard) {
handleEventWithGuard(receiveTime); // 有guard,安全执行回调
}
// guard析构,无操作(对象已析构)
}
weak_ptr::lock():成功则返回非空 shared_ptr(对象存在),失败则返回空(对象已析构);- 仅在回调时加 guard:不影响上层对象的生命周期,仅保证回调执行期间对象存在。
2. update () 的核心流程
update() 是 Channel 与 Poller 交互的核心,流程:
cpp
Channel::update() → EventLoop::updateChannel(Channel*) → Poller::updateChannel(Channel*)
Poller::updateChannel 会根据 Channel 的 events_,调用 epoll_ctl(EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL),将 fd 和关注的事件注册到 epoll 中。
3. Channel 不持有 fd 的设计哲学
- 解耦生命周期:fd 的创建 / 关闭由上层决定(如 TcpConnection 创建 socket fd,连接关闭时 close (fd)),Channel 仅负责事件管理;
- 复用性:同一个 fd 可在不同阶段绑定不同的 Channel(虽实际很少用,但增强灵活性);
- 避免资源泄漏:Channel 析构时不会误关 fd,fd 的关闭逻辑由上层统一管理。
5. 设计亮点总结
| 设计点 | 核心价值 | Reactor 模型中的作用 |
|---|---|---|
| 不持有 fd | 解耦 fd 生命周期与事件管理 | 上层掌控 fd,Channel 专注事件处理 |
| 单 EventLoop 绑定 | 线程安全(EventLoop 单线程) | 避免多线程操作 Channel,简化同步 |
| tie 机制 | 防止回调访问已析构对象 | 保证回调执行的安全性 |
| 事件回调分离 | 读 / 写 / 关闭 / 错误回调独立 | 上层可按需设置回调,逻辑清晰 |
| 事件操作幂等 | 多次 enableReading 无副作用 | 简化上层调用逻辑 |
| 调试友好 | 事件转字符串、logHup 开关 | 方便定位 IO 事件问题 |
二、 Channel.cc
先贴出完整代码,再逐部分解释:
cpp
// Copyright 2010, Shuo Chen. All rights reserved.
// http://code.google.com/p/muduo/
//
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
// 作者:陈硕 (chenshuo at chenshuo dot com)
#include "muduo/base/Logging.h" // 日志库(LOG_TRACE/LOG_WARN 等)
#include "muduo/net/Channel.h" // Channel 头文件
#include "muduo/net/EventLoop.h" // EventLoop 头文件(updateChannel/removeChannel)
#include <sstream> // 字符串流(事件字符串转换)
#include <poll.h> // poll 系统调用相关定义(POLLIN/POLLOUT 等)
using namespace muduo;
using namespace muduo::net;
// 静态常量初始化:无事件(默认关注状态)
const int Channel::kNoneEvent = 0;
// 静态常量初始化:读事件(包含普通数据可读 POLLIN + 紧急数据可读 POLLPRI)
const int Channel::kReadEvent = POLLIN | POLLPRI;
// 静态常量初始化:写事件(POLLOUT 表示可写)
const int Channel::kWriteEvent = POLLOUT;
/// 构造函数:初始化 Channel 核心成员
/// @param loop 所属的 EventLoop
/// @param fd__ 管理的文件描述符
Channel::Channel(EventLoop* loop, int fd__)
: loop_(loop), // 绑定所属的 EventLoop
fd_(fd__), // 初始化管理的 fd(const 不可修改)
events_(0), // 初始无关注的事件
revents_(0), // 初始无触发的事件
index_(-1), // Poller 内部索引初始为 -1(未加入 Poller)
logHup_(true), // 默认记录 HUP 事件日志
tied_(false), // 初始未绑定宿主对象
eventHandling_(false), // 初始未处理事件
addedToLoop_(false) // 初始未添加到 EventLoop 的 Poller
{
}
/// 析构函数:校验 Channel 销毁时的状态(避免非法销毁)
Channel::~Channel()
{
assert(!eventHandling_); // 断言:销毁时未在处理事件(防止析构中执行回调)
assert(!addedToLoop_); // 断言:已从 EventLoop 的 Poller 中移除
if (loop_->isInLoopThread()) // 若在 EventLoop 所在线程
{
assert(!loop_->hasChannel(this)); // 断言:EventLoop 已无当前 Channel 引用
}
}
/// 将 Channel 绑定到宿主对象(防止 handleEvent 时宿主析构)
/// @param obj 宿主对象的 shared_ptr(void 类型兼容任意类型)
void Channel::tie(const std::shared_ptr<void>& obj)
{
tie_ = obj; // 弱引用指向宿主对象(避免循环引用)
tied_ = true; // 标记已绑定
}
/// 更新 Channel 的事件关注状态到 Poller
/// 调用 EventLoop::updateChannel,最终由 Poller 执行 epoll_ctl/poll 修改事件注册
void Channel::update()
{
addedToLoop_ = true; // 标记已添加到 EventLoop
loop_->updateChannel(this); // 转发给 EventLoop 处理
}
/// 将 Channel 从 Poller 中移除
void Channel::remove()
{
assert(isNoneEvent()); // 断言:已禁用所有事件(避免移除时有未处理的事件)
addedToLoop_ = false; // 标记已从 EventLoop 移除
loop_->removeChannel(this); // 转发给 EventLoop 处理
}
/// 处理触发的事件(对外接口,由 EventLoop 调用)
/// 核心逻辑:若绑定了宿主对象,先通过 weak_ptr 锁定宿主,保证回调执行期间宿主不析构
/// @param receiveTime 事件触发的时间戳
void Channel::handleEvent(Timestamp receiveTime)
{
std::shared_ptr<void> guard; // 用于延长宿主对象生命周期的 guard
if (tied_) // 若已绑定宿主对象
{
guard = tie_.lock(); // 弱引用升级为强引用(失败则宿主已析构)
if (guard) // 宿主对象仍存在
{
handleEventWithGuard(receiveTime); // 带 guard 处理事件
}
// 若 guard 为空(宿主已析构),则不处理事件,避免访问悬空指针
}
else // 未绑定宿主对象,直接处理事件
{
handleEventWithGuard(receiveTime);
}
}
/// 实际处理触发的事件(带 guard 保护,确保宿主对象不析构)
/// 处理顺序:HUP → NVAL → ERR → READ → WRITE,优先级从高到低
/// @param receiveTime 事件触发的时间戳
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true; // 标记正在处理事件
LOG_TRACE << reventsToString(); // 日志输出触发的事件(调试用)
// 处理 POLLHUP 事件(连接断开,且无可读数据)
// POLLHUP 表示挂起:socket 对等方关闭连接/关闭写端,且当前无可读数据
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_) // 若开启 HUP 日志
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) // 若设置了关闭回调,执行
{
closeCallback_();
}
}
// 处理 POLLNVAL 事件(文件描述符不是打开的文件/无效)
if (revents_ & POLLNVAL)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
}
// 处理 POLLERR/POLLNVAL 事件(错误/无效 fd)
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) // 若设置了错误回调,执行
{
errorCallback_();
}
}
// 处理读事件:POLLIN(普通数据可读)、POLLPRI(紧急数据可读)、POLLRDHUP(半关闭)
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) // 若设置了读回调,执行(携带时间戳)
{
readCallback_(receiveTime);
}
}
// 处理写事件:POLLOUT(可写)
if (revents_ & POLLOUT)
{
if (writeCallback_) // 若设置了写回调,执行
{
writeCallback_();
}
}
eventHandling_ = false; // 标记事件处理完成
}
/// 调试用:将实际触发的事件(revents_)转为可读字符串
string Channel::reventsToString() const
{
return eventsToString(fd_, revents_);
}
/// 调试用:将关注的事件(events_)转为可读字符串
string Channel::eventsToString() const
{
return eventsToString(fd_, events_);
}
/// 静态辅助函数:将 fd 和事件类型转为可读字符串(如 "6: IN OUT HUP")
/// @param fd 文件描述符
/// @param ev 事件类型(events_ 或 revents_)
/// @return 可读的事件字符串
string Channel::eventsToString(int fd, int ev)
{
std::ostringstream oss;
oss << fd << ": "; // 先输出 fd
if (ev & POLLIN)
oss << "IN "; // 普通数据可读
if (ev & POLLPRI)
oss << "PRI "; // 紧急数据可读(如带外数据)
if (ev & POLLOUT)
oss << "OUT "; // 可写
if (ev & POLLHUP)
oss << "HUP "; // 连接挂起
if (ev & POLLRDHUP)
oss << "RDHUP "; // 读端关闭(TCP 半关闭)
if (ev & POLLERR)
oss << "ERR "; // 错误
if (ev & POLLNVAL)
oss << "NVAL "; // 无效 fd
return oss.str(); // 返回拼接后的字符串
}
1. 代码整体核心逻辑回顾
Channel.cpp 是 Channel 类的具体实现,核心围绕 "安全处理 IO 事件、精准分发回调" 展开:
- 严格的状态校验:通过断言保证 Channel 析构 / 移除时的安全(如处理事件时不能析构、移除前必须禁用所有事件);
- tie 机制落地:用
weak_ptr::lock()确保回调执行时上层对象未析构; - 事件分发优先级:先处理连接 / 错误类事件(HUP/NVAL/ERR),再处理读事件,最后处理写事件,符合网络编程的逻辑;
- 调试友好:将事件类型转换为可读字符串,方便定位问题;
- 与 EventLoop 解耦:仅通过
updateChannel/removeChannel接口交互,不直接依赖 Poller 实现。
2. 逐函数拆解核心实现
1. 态常量初始化
cpp
// 对应头文件中声明的静态常量,源文件中定义(C++类静态成员的规范)
const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = POLLIN | POLLPRI;
const int Channel::kWriteEvent = POLLOUT;
kReadEvent包含POLLIN(普通读)和POLLPRI(紧急数据读,如 TCP 带外数据),覆盖所有读场景;kWriteEvent仅POLLOUT,对应可写事件。
2. 构造函数:初始化状态 + 绑定依赖
cpp
Channel::Channel(EventLoop* loop, int fd__)
: loop_(loop), // 绑定所属的EventLoop(唯一)
fd_(fd__), // 封装的fd(const,不可修改)
events_(0), // 初始无关注事件
revents_(0), // 初始无已发生事件
index_(-1), // Poller中初始索引为-1(未注册)
logHup_(true), // 默认记录HUP事件(调试用)
tied_(false), // 初始未绑定tie
eventHandling_(false),// 初始未处理事件
addedToLoop_(false) // 初始未添加到EventLoop
{
}
核心设计 :所有状态初始化为 "安全值",避免野值导致逻辑错误(如 index_=-1 标识未注册到 Poller)。
3. 析构函数:严格的安全校验
cpp
Channel::~Channel()
{
assert(!eventHandling_); // 断言:处理事件时不能析构(防止回调中析构)
assert(!addedToLoop_); // 断言:析构前必须从EventLoop移除
if (loop_->isInLoopThread())
{
// 若在EventLoop线程中,断言loop中已无当前Channel
assert(!loop_->hasChannel(this));
}
}
核心价值:通过断言提前发现逻辑错误(如忘记移除 Channel 就析构、在事件处理中析构 Channel),避免运行时崩溃。
4. tie 机制:绑定上层对象(防回调悬空)
cpp
void Channel::tie(const std::shared_ptr<void>& obj)
{
tie_ = obj; // 保存上层对象的weak_ptr(不影响其生命周期)
tied_ = true;// 标记已绑定tie
}
tie_是weak_ptr:仅 "观测" 上层对象,不阻止其析构;- 调用时机:上层对象(如 TcpConnection)创建 Channel 后,调用
tie(shared_from_this()),将自身生命周期与 Channel 回调绑定。
5. update/remove:与 EventLoop 交互
cpp
// 更新Channel到EventLoop(注册/修改关注的事件)
void Channel::update()
{
addedToLoop_ = true; // 标记已添加到Loop
loop_->updateChannel(this); // 委托给EventLoop处理(最终调用Poller::updateChannel)
}
// 从EventLoop移除Channel
void Channel::remove()
{
assert(isNoneEvent()); // 断言:移除前必须禁用所有事件(避免Poller还监听fd)
addedToLoop_ = false; // 标记已从Loop移除
loop_->removeChannel(this); // 委托给EventLoop处理(最终调用Poller::removeChannel)
}
核心规则:
update()是 Channel 注册 / 修改事件的唯一入口,所有事件操作(如enableReading)最终都会调用它;remove()前必须调用disableAll()(确保events_=kNoneEvent),否则断言失败 ------ 防止 Poller 还监听 fd 就移除 Channel。
6. handleEvent:事件处理入口(核心)
cpp
void Channel::handleEvent(Timestamp receiveTime)
{
std::shared_ptr<void> guard;
if (tied_)
{
guard = tie_.lock(); // 尝试获取上层对象的shared_ptr
if (guard)
{
// 获取成功:对象存在,安全执行事件处理
handleEventWithGuard(receiveTime);
}
// 获取失败:对象已析构,不执行任何回调(避免野指针)
}
else
{
// 未绑定tie:直接执行
handleEventWithGuard(receiveTime);
}
}
tie 机制的核心落地:
weak_ptr::lock():成功返回非空shared_ptr(对象存在),失败返回空(对象已析构);guard是局部变量:保证回调执行期间,上层对象的引用计数≥1,不会被析构(RAII 风格)。
7. handleEventWithGuard:事件分发核心(优先级 + 回调)
cpp
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true; // 标记正在处理事件(防止处理中移除/析构)
LOG_TRACE << reventsToString(); // 调试日志:打印已发生的事件
// 1. 处理POLLHUP(连接挂起):无POLLIN时,说明连接正常关闭
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_(); // 调用关闭回调
}
// 2. 处理POLLNVAL(fd无效,如未open就监听)
if (revents_ & POLLNVAL)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
}
// 3. 处理错误事件(POLLERR/POLLNVAL)
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_(); // 调用错误回调
}
// 4. 处理读事件(普通读/紧急读/半关闭)
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) readCallback_(receiveTime); // 调用读回调(带时间戳)
}
// 5. 处理写事件(可写)
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_(); // 调用写回调
}
eventHandling_ = false; // 标记事件处理完成
}
核心设计:事件分发优先级(从严重到普通):
- 连接挂起(POLLHUP)→ 2. fd 无效(POLLNVAL)→ 3. 错误(POLLERR)→ 4. 读事件 → 5. 写事件;
- 这个优先级符合网络编程逻辑:先处理连接 / 错误类事件,再处理数据读写;
POLLRDHUP:TCP 半关闭(对方关闭写端),归为读事件处理,上层可感知连接关闭。
8. 调试辅助:事件转字符串
cpp
// 已发生事件转字符串(如 "123: IN HUP")
string Channel::reventsToString() const
{
return eventsToString(fd_, revents_);
}
// 关注的事件转字符串
string Channel::eventsToString() const
{
return eventsToString(fd_, events_);
}
// 核心实现:将事件掩码转换为可读字符串
string Channel::eventsToString(int fd, int ev)
{
std::ostringstream oss;
oss << fd << ": ";
if (ev & POLLIN) oss << "IN ";
if (ev & POLLPRI) oss << "PRI ";
if (ev & POLLOUT) oss << "OUT ";
if (ev & POLLHUP) oss << "HUP ";
if (ev & POLLRDHUP) oss << "RDHUP ";
if (ev & POLLERR) oss << "ERR ";
if (ev & POLLNVAL) oss << "NVAL ";
return oss.str();
}
核心价值 :调试时可直接打印事件类型(如 LOG_TRACE << channel->reventsToString()),快速定位 "fd 123 触发了 IN + HUP 事件" 这类问题,大幅降低调试成本。
3. 核心设计亮点总结
| 设计点 | 解决的问题 | 安全 / 性能提升 |
|---|---|---|
| 严格的断言校验 | 析构时未移除、处理事件时析构、移除前未禁用事件等逻辑错误 | 提前发现错误,避免运行时崩溃 |
| tie 机制(weak_ptr + lock) | 回调执行时上层对象已析构(野指针访问) | 保证回调执行期间对象存在,避免崩溃 |
| 事件分发优先级 | 错误 / 关闭事件未及时处理导致数据读写异常 | 符合网络编程逻辑,优先处理关键事件 |
| 调试友好的事件字符串 | 事件掩码(如 0x2)无法直观理解 | 快速定位触发的事件类型,降低调试成本 |
| 状态标记(eventHandling_/addedToLoop_) | 并发 / 重入操作导致的状态混乱 | 单线程下通过状态标记避免非法操作 |
总结
- 安全第一:通过断言、tie 机制、状态标记,确保 Channel 在析构、事件处理、移除时的安全性,避免野指针和逻辑错误;
- 事件分发逻辑:按 "连接 / 错误→读→写" 的优先级处理事件,符合网络编程的核心逻辑;
- 解耦设计:不直接操作 Poller,仅通过 EventLoop 接口交互,适配不同的 IO 多路复用器(epoll/poll);
- 调试友好:事件转字符串功能,大幅降低 IO 事件相关问题的定位成本。