仿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;

这里没有包含头文件,而只是前向声明PollerEventLoop 类。

这样做的好处:

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

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 的事件类型(如 EPOLLINEPOLLOUT)都是宏定义的位标志,可以用一个整数按位组合。


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
  • EPOLLIN1(二进制 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.cppEventLoop.cpp 中)。

它们的作用:

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

在 muduo 中,UpdateRemove 必须保证线程安全,通常会通过 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 是一个"兜底"回调,无论发生什么事件都会调用。

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

相关推荐
末代iOS程序员华仔1 小时前
Codex + Figma 生成 Objective‑C (UIKit) 完整工作流
c语言·开发语言·figma
ybdesire1 小时前
注入型漏洞以及agent-audit的检测原理
网络·安全·风险·智能体·ai安全
末代iOS程序员华仔1 小时前
Cursor 编辑器 + Figma‑MCP 服务器(真正让 Codex 直接读取 Figma 链接,高级方案)
服务器·编辑器·figma
SomeB1oody1 小时前
【RustyML入门】7.3. 性能调优与并行
开发语言·后端·机器学习·rust·教程
小此方1 小时前
Linux加餐(二):藏在Linux中的设计模式(二)单例模式与线程池
linux·单例模式·设计模式
余额瞒着我当琳1 小时前
C++ list第二讲数据结构修炼:迭代器源码 + 栈队列适配器 + LeetCode 三道高频题
数据结构·c++·list
李白你好1 小时前
今日网络技术热点:【计算机网络 | 数据链路层8:CSMA/CD
网络·计算机网络
猫吃了源码1 小时前
Linux系统制作 Ubuntu Server 24.04 启动U盘详细教程
linux·服务器·ubuntu
hehelm1 小时前
仿muduo库实现高并发服务器—EventLoop
linux·服务器·网络·c++