Poller 封装详解
一、整体结构
#define MAX_EPOLLEVENTS 1024
class Poller {
private:
int _epfd;
struct epoll_event _evs[MAX_EPOLLEVENTS];
std::unordered_map<int, Channel *> _channels;
private:
void Update(Channel *channel, int op);
bool HasChannel(Channel *channel);
public:
Poller();
void UpdateEvent(Channel *channel);
void RemoveEvent(Channel *channel);
void Poll(std::vector<Channel*> *active);
};
对外提供的接口只有三个:
UpdateEvent:添加或修改监控事件RemoveEvent:移除监控Poll:开始等待事件,返回活跃连接
内部用 _channels 维护 fd -> Channel* 的映射,方便根据 fd 找到对应的 Channel 对象。
二、逐块详细讲解
1. 宏定义和成员变量
#define MAX_EPOLLEVENTS 1024
MAX_EPOLLEVENTS 表示一次 epoll_wait 最多可以返回多少个就绪事件。
这个值通常设置得大一些,避免事件太多导致溢出。1024 是一个比较常见的默认值。
int _epfd;
_epfd 是 epoll_create 返回的 epoll 实例文件描述符,所有 epoll_ctl 和 epoll_wait 都围绕它进行。
struct epoll_event _evs[MAX_EPOLLEVENTS];
_evs 是一个 epoll_event 数组,用于接收 epoll_wait 返回的已触发事件。
因为 epoll_wait 需要用户提供一块内存来存放事件,这里直接使用成员数组,避免每次动态分配。
std::unordered_map<int, Channel *> _channels;
_channels 保存 fd 到 Channel* 的映射。
当 epoll_wait 返回时,我们只知道哪些 fd 有事件,需要根据 fd 找到对应的 Channel 对象,才能调用它的回调。
unordered_map 查找效率平均 O(1),适合频繁查找。
2. 私有函数:Update
void Update(Channel *channel, int op) {
// int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
int fd = channel->Fd();
struct epoll_event ev;
ev.data.fd = fd;
ev.events = channel->Events();
int ret = epoll_ctl(_epfd, op, fd, &ev);
if (ret < 0) {
ERR_LOG("EPOLLCTL FAILED!");
}
return;
}
这个私有函数封装了 epoll_ctl,供外部调用。
epoll_ctl参数说明:_epfd:epoll 实例。op:操作类型,可以是EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL。fd:要操作的目标文件描述符。ev:一个epoll_event结构体,描述要监听的事件和附加数据。
ev.data.fd = fd:把 fd 存入data,这样当事件触发时,我们可以从data.fd知道是哪个 fd 就绪了。ev.events = channel->Events():设置监听事件,就是Channel里通过EnableRead()等维护的_events。
3. 私有函数:HasChannel
bool HasChannel(Channel *channel) {
auto it = _channels.find(channel->Fd());
if (it == _channels.end()) {
return false;
}
return true;
}
判断一个 Channel 是否已经被添加到 Poller 中。
原理很简单:在 _channels 中查找 fd,如果找到就返回 true。
4. 构造函数
Poller() {
_epfd = epoll_create(MAX_EPOLLEVENTS);
if (_epfd < 0) {
ERR_LOG("EPOLL CREATE FAILED!!");
abort();
}
}
构造函数创建 epoll 实例。
epoll_create(MAX_EPOLLEVENTS):
早期的 Linux 内核需要指定一个"期望监控的文件描述符数量",但现在这个参数实际上已被忽略(只要大于 0 即可)。
推荐使用epoll_create1(EPOLL_CLOEXEC),可以自动设置 close-on-exec 标志。- 如果创建失败,打印日志并
abort()退出程序。
abort()会生成核心转储并终止进程,适合不可恢复的致命错误。
5. 对外接口:UpdateEvent
void UpdateEvent(Channel *channel) {
bool ret = HasChannel(channel);
if (ret == false) {
// 不存在则添加
_channels.insert(std::make_pair(channel->Fd(), channel));
return Update(channel, EPOLL_CTL_ADD);
}
return Update(channel, EPOLL_CTL_MOD);
}
这个函数负责添加或修改监控事件,是 Channel::Update() 最终会调用的函数。
- 如果
_channels中没有这个 fd,说明是第一次监控,执行EPOLL_CTL_ADD,并把它加入 map。 - 如果已经存在,说明之前监控过,现在只是修改事件,执行
EPOLL_CTL_MOD。
6. 对外接口:RemoveEvent
void RemoveEvent(Channel *channel) {
auto it = _channels.find(channel->Fd());
if (it != _channels.end()) {
_channels.erase(it);
}
Update(channel, EPOLL_CTL_DEL);
}
这个函数从 Poller 中移除一个 Channel。
- 先从
_channels中删除 fd 映射。 - 然后调用
epoll_ctl从内核 epoll 中删除该 fd。
7. 对外接口:Poll
void Poll(std::vector<Channel*> *active) {
// int epoll_wait(int epfd, struct epoll_event *evs, int maxevents, int timeout)
int nfds = epoll_wait(_epfd, _evs, MAX_EPOLLEVENTS, -1);
if (nfds < 0) {
if (errno == EINTR) {
return;
}
ERR_LOG("EPOLL WAIT ERROR:%s\n", strerror(errno));
abort();
}
for (int i = 0; i < nfds; i++) {
auto it = _channels.find(_evs[i].data.fd);
assert(it != _channels.end());
it->second->SetREvents(_evs[i].events);
active->push_back(it->second);
}
return;
}
这是最核心的函数,负责等待事件并收集活跃连接。
epoll_wait(_epfd, _evs, MAX_EPOLLEVENTS, -1):- 参数
-1表示永久阻塞,直到有事件发生。 - 返回就绪的 fd 数量
nfds。
- 参数
- 如果返回
nfds < 0:EINTR表示被信号打断,直接返回,不算错误。- 其他错误打印日志并
abort()。
- 遍历就绪事件:
_evs[i].data.fd就是在Update中设置的 fd。- 通过
_channels找到对应的Channel*。 - 调用
SetREvents(_evs[i].events)设置实际发生的事件。 - 把该
Channel*加入active列表,返回给调用者。

5365

被折叠的 条评论
为什么被折叠?



