【项目篇】从零手写高并发服务器(五):Channel事件管理与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串起来,实现事件驱动。

相关推荐
AC赳赳老秦2 小时前
OpenClaw核心命令详解(常用指令+实战示例,高效开启自动化工作)
大数据·运维·人工智能·自动化·ai-native·deepseek·openclaw
二年级程序员2 小时前
认识与了解 C++
开发语言·c++
RoboWizard2 小时前
解锁高效办公新体验 金士顿高速闪存盘
运维·服务器·网络·缓存·智能手机
TsukasaNZ2 小时前
代码性能剖析工具
开发语言·c++·算法
cccyi72 小时前
仿 muduo 库 one thread one loop 式并发服务器实现
reactor
赋创小助手2 小时前
NVIDIA RTX PRO 4500 Blackwell Server Edition 深度解析:AI服务器新一代“高密度算力卡”?
服务器·人工智能·科技·深度学习·计算机视觉·语言模型·自然语言处理
user_admin_god2 小时前
服务器安装向量数据库-Docker版本
服务器·数据库·docker
阿杜杜不是阿木木2 小时前
从0到1构建像Claude Code那样的Agent(三):行动前先计划
java·服务器·windows·agent·ai编程·claudecode
蓝队云计算2 小时前
部署OpenClaw选什么服务器?2核4G+10M带宽配置的蓝队云服务器轻松搞定!
运维·服务器·人工智能·云服务器·openclaw