仿muduo库实现高并发服务器—Channel类

Channel 封装详解

一、整体结构

cpp 复制代码
class Poller;
class EventLoop;

class Channel {
private:
    int _fd;
    EventLoop *_loop;
    uint32_t _events;  // 当前需要监控的事件
    uint32_t _revents; // 当前连接触发的事件

    using EventCallback = std::function<void()>;
    EventCallback _read_callback;
    EventCallback _write_callback;
    EventCallback _error_callback;
    EventCallback _close_callback;
    EventCallback _event_callback;

public:
    Channel(EventLoop *loop, int fd);
    int Fd();
    uint32_t Events();
    void SetREvents(uint32_t events);
    void SetReadCallback(const EventCallback &cb);
    void SetWriteCallback(const EventCallback &cb);
    void SetErrorCallback(const EventCallback &cb);
    void SetCloseCallback(const EventCallback &cb);
    void SetEventCallback(const EventCallback &cb);

    bool ReadAble();
    bool WriteAble();
    void EnableRead();
    void EnableWrite();
    void DisableRead();
    void DisableWrite();
    void DisableAll();
    void Remove();
    void Update();

    void HandleEvent();
};

可以看到,它对外提供了很多设置回调和修改事件监控的接口。

它的核心函数只有两个:Update() 和 HandleEvent()。

前者把 Channel 的 _events 同步到 Poller,后者在事件发生时执行回调。


二、逐块详细讲解

1. 前向声明

cpp 复制代码
class Poller;
class EventLoop;

这里没有包含头文件,而只是前向声明 了 Poller 和 EventLoop 类。

这样做的好处:

  • 减少头文件依赖,加快编译速度
  • 避免循环包含

在 Channel 中只需要用到 EventLoop* 指针,不需要知道 EventLoop 的具体定义,所以前向声明就够了。


2. 成员变量

cpp 复制代码
int _fd;
EventLoop *_loop;
uint32_t _events;  // 当前需要监控的事件
uint32_t _revents; // 当前连接触发的事件
  • _fd:这个 Channel 负责的文件描述符,可以是监听 socket 或已连接 socket。
  • _loop:指向所属的 EventLoop 对象。Channel 需要把事件变化通知给 EventLoop,让 EventLoop 去更新 Poller。
  • _events:希望监控的事件集合 。比如想让 epoll 监听这个 fd 的可读事件,就设置 _events |= EPOLLIN。
  • _revents:实际发生的事件集合 。当 epoll_wait 返回时,内核会告诉我们这个 fd 发生了什么事件,Poller 会调用 SetREvents() 设置 _revents。

uint32_t 是 32 位无符号整数,用来存储位掩码。

epoll 的事件类型(如 EPOLLIN、EPOLLOUT)都是宏定义的位标志,可以用一个整数按位组合。


3. 回调函数类型与成员

cpp 复制代码
using EventCallback = std::function<void()>;
EventCallback _read_callback;
EventCallback _write_callback;
EventCallback _error_callback;
EventCallback _close_callback;
EventCallback _event_callback;

std::function<void()> 是 C++11 引入的一个通用函数包装器,可以保存:

  • 普通函数指针
  • 成员函数指针(配合 std::bind)
  • lambda 表达式
  • 函数对象

这里 EventCallback 被定义为"无参数、无返回值"的可调用对象。

Channel 用五个这样的回调成员来分别处理不同的事件:

  • _read_callback:可读事件触发时调用。
  • _write_callback:可写事件触发时调用。
  • _error_callback:错误事件触发时调用。
  • _close_callback:连接关闭事件触发时调用。
  • _event_callback:任意事件触发后都会调用,可以理解为"兜底"回调。

4. 构造函数

cpp 复制代码
Channel(EventLoop *loop, int fd)
    : _fd(fd), _events(0), _revents(0), _loop(loop)
{}

初始化列表把 _fd 设成传入的 fd,_events 和 _revents 初始化为 0,_loop 保存事件循环指针。

注意:这里没有设置任何事件,也没有注册到 Poller。

后续需要调用 EnableRead() 等函数才会真正让 EventLoop 去关注这个 fd。


5. 简单的 getter / setter

cpp 复制代码
int Fd() { return _fd; }
uint32_t Events() { return _events; }
void SetREvents(uint32_t events) { _revents = events; }
  • Fd():返回 fd。
  • Events():返回当前希望监控的事件集合。
  • SetREvents():由 Poller 在事件发生后调用,设置实际发生的事件集合。

6. 设置回调函数

cpp 复制代码
void SetReadCallback(const EventCallback &cb) { _read_callback = cb; }
void SetWriteCallback(const EventCallback &cb) { _write_callback = cb; }
void SetErrorCallback(const EventCallback &cb) { _error_callback = cb; }
void SetCloseCallback(const EventCallback &cb) { _close_callback = cb; }
void SetEventCallback(const EventCallback &cb) { _event_callback = cb; }

这些函数很简单,就是把外部传进来的回调赋值给对应成员。

参数用 const EventCallback & 引用传递,避免拷贝。


7. 判断是否监控了可读/可写

cpp 复制代码
bool ReadAble() { return (_events & EPOLLIN); }
bool WriteAble() { return (_events & EPOLLOUT); }

& 是按位与运算。

_events & EPOLLIN 如果结果非零,说明 _events 中包含了 EPOLLIN 这个标志位,也就是当前正在监控可读事件。

例如:

  • _events = EPOLLIN | EPOLLOUT,二进制 ...0011
  • EPOLLIN 是 1(二进制 0001)
  • _events & EPOLLIN 结果为 1,非零,返回 true。

8. 修改监控事件:Enable / Disable

cpp 复制代码
void EnableRead() { _events |= EPOLLIN; Update(); }
void EnableWrite() { _events |= EPOLLOUT; Update(); }
void DisableRead() { _events &= ~EPOLLIN; Update(); }
void DisableWrite() { _events &= ~EPOLLOUT; Update(); }
void DisableAll() { _events = 0; Update(); }

这些函数用来修改 _events 并立即调用 Update() 通知 EventLoop 更新 Poller。

  • |= 按位或赋值:把某个标志位加上去,不影响其他位。
  • &= ~ 按位与取反:把某个标志位清零,不影响其他位。

比如 EnableRead():

  1. _events |= EPOLLIN:给 _events 增加可读标志。
  2. Update():告诉 EventLoop:"我感兴趣的事件变了,请重新设置 epoll 监听"。

DisableAll() 直接把 _events 设为 0,表示不再关心任何事件。


9. Remove() 和 Update()

cpp 复制代码
void Remove();
void Update();

这里只是声明,定义在别的源文件中(通常在 Channel.cpp 或 EventLoop.cpp 中)。

它们的作用:

  • Update():把 Channel 的 _events 同步到 EventLoop,进而调用 Poller 的 UpdateChannel 修改 epoll 注册。
  • Remove():把该 Channel 从 EventLoop/Poller 中移除,不再监听任何事件。

在 muduo 中,Update 和 Remove 必须保证线程安全,通常会通过 EventLoop 的任务队列在 IO 线程中执行。


10. HandleEvent():事件分发核心

cpp 复制代码
void HandleEvent() {
    if ((_revents & EPOLLIN) || (_revents & EPOLLRDHUP) || (_revents & EPOLLPRI)) {
        if (_read_callback) _read_callback();
    }
    /*有可能会释放连接的操作事件,一次只处理一个*/
    if (_revents & EPOLLOUT) {
        if (_write_callback) _write_callback();
    } else if (_revents & EPOLLERR) {
        if (_error_callback) _error_callback();
    } else if (_revents & EPOLLHUP) {
        if (_close_callback) _close_callback();
    }
    if (_event_callback) _event_callback();
}

这个函数在 Poller 检测到某个 fd 有事件发生后被调用。

它根据 _revents 中的标志来决定调用哪个回调。

(1) 首先处理读相关事件
cpp 复制代码
if ((_revents & EPOLLIN) || (_revents & EPOLLRDHUP) || (_revents & EPOLLPRI)) {
    if (_read_callback) _read_callback();
}

这里把三种情况都归为"可读":

  • EPOLLIN:普通可读事件,内核接收缓冲区有数据可读。
  • EPOLLRDHUP:对端关闭连接或半关闭(比如调用了 shutdown(SHUT_WR))。
  • EPOLLPRI:有带外数据(OOB,紧急数据)到达。

遇到这些情况,都调用 _read_callback。

(2) 然后处理写/错误/挂起
cpp 复制代码
if (_revents & EPOLLOUT) {
    if (_write_callback) _write_callback();
} else if (_revents & EPOLLERR) {
    if (_error_callback) _error_callback();
} else if (_revents & EPOLLHUP) {
    if (_close_callback) _close_callback();
}
  • EPOLLOUT:可写事件,发送缓冲区未满,可以继续发送。
  • EPOLLERR:发生错误。
  • EPOLLHUP:连接挂起(比如对端直接断开)。
(3) 最后调用任意事件回调
cpp 复制代码
if (_event_callback) _event_callback();

_event_callback 是一个"兜底"回调,无论发生什么事件都会调用。

它可以在事件处理后做一些统一的清理或日志工作。

相关推荐
Liquad Li1 小时前
Salesforce 主流认证方式、流程、适用场景
开发语言·salesforce
LOVE️YOU4 小时前
Python 中 Tuple 为什么有时可 Hash,有时不可 Hash?
开发语言·python·哈希算法
木卫二号Coding4 小时前
推荐Linux统计文件大小并操作删除工具-ncdu
linux·运维·服务器
李永奉4 小时前
C语言-指针函数与函数指针及回调函数的使用
c语言·开发语言
小oo呆4 小时前
【学习心得】迭代器和可迭代对象
开发语言·python
AC赳赳老秦4 小时前
采集行为合规自检:OpenClaw 自动校验 robots 协议与采集频率,规避违规采集风险
java·开发语言·c++·python·php·deepseek·openclaw
钱栈up5 小时前
mvn compile 卡死在 javac 阶段:一次类型不匹配的排查与修复
java·开发语言
迅猛龙办公室5 小时前
Python输出当前计算机的系统日期和时间
开发语言·前端·python
无名猿5 小时前
C++ 查找算法:std::find 与容器成员 find 的性能差距
c++·性能优化·stl·标准库
杨丰玮4185 小时前
C语言基础知识(一)————各类符号的含义与输入输出库(stdio)
c语言·开发语言