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

相关推荐
Ares-Wang2 小时前
Linux》》systemd 、service、systemctl daemon-reload、systemctl restart docker
linux·运维·docker
初夏睡觉4 小时前
c++1.3(变量与常量,简单数学运算详解),草稿公放
开发语言·c++
安审若无4 小时前
运维知识框架
运维·服务器
阿拉斯攀登4 小时前
从入门到实战:CMake 与 Android JNI/NDK 开发全解析
android·linux·c++·yolo·cmake
Arvin6277 小时前
Nginx 添加账号密码访问验证
运维·服务器·nginx
风曦Kisaki7 小时前
# Linux 磁盘查看命令详解:df 与 du
linux·运维·网络
攻城狮在此7 小时前
华为交换机Console口密码如何清除
运维·网络·华为
筱璦7 小时前
期货软件开发 - C# 调用 HQChart 指标计算 C++ 动态库
c++·microsoft·c#
不想写代码的星星8 小时前
C++ 内存管理:分区、自定义分配器、常见问题与检测工具
c++
内心的一片海8 小时前
服务器内存异常占用
运维·服务器