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,二进制...0011EPOLLIN是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():
_events |= EPOLLIN:给_events增加可读标志。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 是一个"兜底"回调,无论发生什么事件都会调用。
它可以在事件处理后做一些统一的清理或日志工作。