文章目录
-
- 从零手写高并发服务器(五):Channel事件管理与Poller模块
- 一、epoll快速回顾
-
- [1.1 epoll三个核心函数](#1.1 epoll三个核心函数)
- [1.2 epoll_event结构](#1.2 epoll_event结构)
- 二、Channel模块
-
- [2.1 Channel的作用](#2.1 Channel的作用)
- [2.2 Channel类实现](#2.2 Channel类实现)
- 三、Poller模块
-
- [3.1 Poller的作用](#3.1 Poller的作用)
- [3.2 Poller类实现](#3.2 Poller类实现)
- [3.3 Channel中Update和Remove的实现说明](#3.3 Channel中Update和Remove的实现说明)
- 四、Poller模块单独测试
- 五、提交代码
- 六、本篇总结
从零手写高并发服务器(五):Channel事件管理与Poller模块
💬 开篇:这一篇我们进入Reactor模式的核心------事件管理。Channel负责管理一个文件描述符的事件(可读、可写、错误等),Poller负责封装epoll进行事件监控。这两个模块是整个服务器的"眼睛",负责发现哪些连接有事件需要处理。
👍 点赞、收藏与分享:理解了Channel和Poller,你就理解了Reactor模式的一半。
🚀 循序渐进:epoll回顾 → Channel设计 → Poller设计 → 联合测试。
一、epoll快速回顾
1.1 epoll三个核心函数
cpp
// 创建epoll实例
int epoll_create(int size); // size已被忽略,传任意正数即可
// 控制epoll:添加/修改/删除监控
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// op: EPOLL_CTL_ADD / EPOLL_CTL_MOD / EPOLL_CTL_DEL
// 等待事件就绪
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
1.2 epoll_event结构
cpp
struct epoll_event {
uint32_t events; // 事件类型
epoll_data_t data; // 用户数据
};
// 常用事件类型
EPOLLIN // 可读
EPOLLOUT // 可写
EPOLLERR // 错误
EPOLLHUP // 挂断
EPOLLRDHUP // 对端关闭
EPOLLET // 边缘触发模式
二、Channel模块
2.1 Channel的作用
Channel是对一个文件描述符的事件管理的封装:
- 保存这个fd当前关注的事件(
_events) - 保存这个fd实际就绪的事件(
_revents) - 保存各种事件的回调函数(读、写、错误、关闭)
- 提供启用/禁用事件监控的接口
bash
Channel的职责:
┌─────────────────────────────────────┐
│ Channel │
│ │
│ fd = 5 │
│ _events = EPOLLIN (关注可读) │
│ _revents = 0 (当前无就绪事件) │
│ │
│ _read_callback = 读事件处理函数 │
│ _write_callback = 写事件处理函数 │
│ _error_callback = 错误处理函数 │
│ _close_callback = 关闭处理函数 │
│ │
│ EnableRead() → 启动读事件监控 │
│ HandleEvent() → 根据就绪事件调用回调 │
└─────────────────────────────────────┘
2.2 Channel类实现
继续编辑 server.hpp,在Socket类后面添加:
cpp
// ==================== Channel事件管理模块 ====================
#include <sys/epoll.h>
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) : _fd(fd), _events(0), _revents(0), _loop(loop) {}
int Fd() { return _fd; }
uint32_t Events() { return _events; } // 获取想要监控的事件
void SetREvents(uint32_t events) { _revents = events; } // 设置实际就绪的事件
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; }
// 当前是否监控了可读
bool ReadAble() { return (_events & EPOLLIN); }
// 当前是否监控了可写
bool WriteAble() { return (_events & EPOLLOUT); }
// 启动读事件监控
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(); }
// 移除监控
void Remove(){};
// 更新监控
void Update(){};
// 事件处理,一旦连接触发了事件,就调用这个函数
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();
}
};
注意 :Remove() 和 Update() 方法需要调用EventLoop的接口,我们稍后实现。
三、Poller模块
3.1 Poller的作用
Poller是对epoll的封装,负责:
- 管理epoll实例
- 添加/修改/删除fd的事件监控
- 等待事件就绪,返回活跃的Channel列表
3.2 Poller类实现
cpp
// ==================== Poller模块(epoll封装)====================
#define MAX_EPOLLEVENTS 1024
class Poller {
private:
int _epfd;
struct epoll_event _evs[MAX_EPOLLEVENTS];
std::unordered_map<int, Channel*> _channels;
private:
// 对epoll的直接操作
void Update(Channel *channel, int op) {
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;
}
// 判断一个Channel是否已经被管理
bool HasChannel(Channel *channel) {
auto it = _channels.find(channel->Fd());
if (it == _channels.end()) {
return false;
}
return true;
}
public:
Poller() {
_epfd = epoll_create(MAX_EPOLLEVENTS);
if (_epfd < 0) {
ERR_LOG("EPOLL CREATE FAILED!!");
abort(); // 退出程序
}
}
// 添加或修改监控事件
void UpdateEvent(Channel *channel) {
bool ret = HasChannel(channel);
if (ret == false) {
// 不存在则添加
_channels[channel->Fd()] = channel;
return Update(channel, EPOLL_CTL_ADD);
}
return Update(channel, EPOLL_CTL_MOD);
}
// 移除监控
void RemoveEvent(Channel *channel) {
auto it = _channels.find(channel->Fd());
if (it != _channels.end()) {
_channels.erase(it);
}
Update(channel, EPOLL_CTL_DEL);
}
// 开始监控,返回活跃连接
void Poll(std::vector<Channel*> *active) {
// timeout设置为-1表示阻塞监控,设置为3000表示3秒超时
int nfds = epoll_wait(_epfd, _evs, MAX_EPOLLEVENTS, 3000);
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;
}
};
3.3 Channel中Update和Remove的实现说明
前面Channel类中 Update() 和 Remove() 留了个坑,这两个方法需要通过EventLoop来调用Poller。因为Channel不直接持有Poller指针,而是通过EventLoop间接操作。
这个我们在下一篇实现EventLoop的时候补上,现在先知道它们的作用:
Update():告诉Poller,我这个Channel的监控事件变了,帮我更新一下Remove():告诉Poller,我不需要监控了,帮我移除
四、Poller模块单独测试
虽然Channel的Update/Remove还没接上EventLoop,但我们可以直接用Poller来测试epoll封装是否正确:
bash
cd ~/TcpServer/test
vim poller_test.cpp
cpp
#include "../source/server.hpp"
int main() {
// 创建一个监听套接字
Socket srv_sock;
srv_sock.CreateServer(8500);
DBG_LOG("服务器启动成功,监听端口8500,fd=%d", srv_sock.Fd());
// 创建Poller
Poller poller;
// 创建Channel,手动关联
Channel channel(NULL, srv_sock.Fd()); // 这里EventLoop先传NULL,因为还没实现
channel.EnableRead(); // 设置关注可读事件
// 手动添加到Poller中监控
poller.UpdateEvent(&channel);
DBG_LOG("已将监听套接字添加到epoll监控");
while (1) {
std::vector<Channel*> actives;
poller.Poll(&actives);
for (auto &ch : actives) {
DBG_LOG("fd=%d 有事件就绪!", ch->Fd());
// 手动accept
int newfd = accept(ch->Fd(), NULL, NULL);
if (newfd >= 0) {
DBG_LOG("获取新连接,fd=%d", newfd);
close(newfd);
}
}
}
return 0;
}
编译运行:
bash
g++ -std=c++11 poller_test.cpp -o poller_test -lpthread
./poller_test
然后另开一个终端用之前写的客户端测试:
bash
cd ~/TcpServer/test
./tcp_cli
服务端输出:
bash
wsh@VM-16-2-ubuntu:~/TcpServer/test$ ./poller_test
[0x7f8a1b2c3740 16:05:11 ../source/server.hpp:156] SIGPIPE INIT
[0x7f8a1b2c3740 16:05:11 poller_test.cpp:7] 服务器启动成功,监听端口8500,fd=3
[0x7f8a1b2c3740 16:05:11 poller_test.cpp:16] 已将监听套接字添加到epoll监控
[0x7f8a1b2c3740 16:05:18 poller_test.cpp:23] fd=3 有事件就绪!
[0x7f8a1b2c3740 16:05:18 poller_test.cpp:27] 获取新连接,fd=5
Poller模块工作正常!epoll封装没问题。
五、提交代码
bash
cd ~/TcpServer
git add .
git commit -m "实现Channel事件管理和Poller(epoll封装)模块"
git push
六、本篇总结
| 模块 | 功能 |
|---|---|
| Channel | 管理单个fd的事件和回调函数 |
| Poller | 封装epoll,负责事件监控 |
当前 server.hpp 结构:
cpp
// 日志宏
// Buffer类
// NetWork类(SIGPIPE处理)
// Socket类
// Channel类(Update/Remove待补)
// Poller类
💬 下一篇预告:实现EventLoop事件循环------Reactor模式的心脏!把Channel、Poller串起来,实现事件驱动。