拓冰建站拓冰建站
首页 / 资讯中心 / 正文

仿muduo高并发网络库——Poller和EpollPoller

一、PollerIO 多路复用抽象基类// Poller 是抽象基类可以用 epoll 也可以用 poll 实现 class Poller { public: Poller(); virtual ~Poller();作用 定义抽象基类- virtual ~Poller() 虚析构函数确保派生类的析构函数被调用为什么用抽象基类- 可以用不同的 IO 多路复用实现epoll、poll、select- 保留了抽象接口方便扩展//更新Channel在epoll中的注册 virtual void updateChannel(Channel* channel) 0;作用 纯虚函数更新 Channel 的注册- 0 纯虚函数派生类必须实现- 参数Channel* 要更新的 Channel使用场景 - Channel::enableReading() →update() →EventLoop::updateChannel() →Poller::updateChannel()//等待事件发生返回有事件的Channel列表 virtual void poll(int timeoutMs, std::vectorChannel**activeChannels) 0;作用 纯虚函数等待事件发生- timeoutMs 超时时间毫秒- activeChannels 输出参数存储有事件的 Channel使用场景 - EventLoop::loop() 中调用protected: std::mapint, Channel* channels_; };作用 成员变量- channels_ fd → Channel 的映射- protected 派生类可以访问为什么需要这个映射- epoll_wait 返回的是epoll_event 包含data.ptr Channel 指针- 但updateChannel 需要知道 fd 是否已经注册- 用map 快速查找二、EpollPollerepoll实现1.EpollPoller.h//实现Poller的两个纯虚函数 void updateChannel(Channel* channel) override; void poll(int timeoutMs, std::vectorChannel** activeChannels) override;作用 实现基类的纯虚函数- updateChannel 更新 Channel 的 epoll 注册- poll 等待事件发生private: //把Channel的events_转成epoll_event,调用epoll_ctl void update(int operation, Channel* channel);作用 私有辅助方法- 封装epoll_ctl 调用- operation EPOLL_CTL_ADD 、EPOLL_CTL_MOD 、EPOLL_CTL_DELint epollfd_; //epoll的文件描述符epoll_create返回的 std::vectorstruct epoll_event events_; //存放epoll_wait返回的事件 };作用 成员变量- epollfd_ epoll 实例的文件描述符- events_ 存储epoll_wait 返回的事件数组为什么用vector 而不是数组- vector 可以动态调整大小- 如果活跃连接数超过初始容量可以扩容2.EpollPoller.cppEpollPoller::EpollPoller() : epollfd_(epoll_create1(EPOLL_CLOEXEC)) //创建epoll实例 ,events_(kInitEventListSize) //初始分配16个空间 { if (epollfd_ 0) { printf(epoll_create1 failed!\n); } }作用 构造函数- epoll_create1(EPOLL_CLOEXEC) 创建 epoll 实例- EPOLL_CLOEXEC 设置 close-on-exec 标志防止子进程继承- events_(kInitEventListSize) 初始化events_ 数组大小为 16epoll_create1 vsepoll_create - epoll_create(size) 旧版本size 参数已废弃- epoll_create1(flags) 新版本支持标志位为什么检查epollfd_ 0 - epoll_create1 失败返回 -1- 可能的原因系统资源不足、内核不支持 epollEpollPoller::~EpollPoller() { close(epollfd_); }作用 析构函数- 关闭 epoll 文件描述符- 内核会自动清理 epoll 实例懒删除下次调用epoll时才会进行清理多线程下可能导致fd复用失败//等待事件 void EpollPoller::poll(int timeoutMs, std::vectorChannel** activeChannels) { int numEvents epoll_wait(epollfd_, *events_.begin(), events_.size(), timeoutMs);作用 等待事件发生- epollfd_ epoll 实例- *events_.begin() 事件数组的首地址- events_.size() 数组大小- timeoutMs 超时时间毫秒- 返回值发生的事件数量epoll_wait 参数详解- epfd epoll 实例- events 输出参数存储发生的事件- maxevents 最多返回的事件数量- timeout 超时时间- -1 永久阻塞- 0 立即返回非阻塞- 0 阻塞指定毫秒数为什么用*events_.begin() - events_.begin() 返回迭代器- *events_.begin() 解引用得到第一个元素- *events_.begin() 取地址得到指针- 等价于events_.data() C11if(numEvents 0) { // 遍历所有就绪的事件 for (int i 0; i numEvents; i) { //从epoll_event 里取出Channel指针 Channel* channel (Channel*)events_[i].data.ptr;作用 遍历发生的事件- events_[i].data.ptr 取出存储的Channel 指针- 强制转换为Channel*epoll_event 结构struct epoll_event { uint32_t events; // 发生的事件 epoll_data_t data; // 用户数据 }; typedef union epoll_data { void* ptr; // 指针 int fd; // 文件描述符 uint32_t u32; // 32位整数 uint64_t u64; // 64位整数 } epoll_data_t;为什么用data.ptr 而不是data.fd - 用ptr 可以直接存储Channel 指针避免查找- 用fd 需要额外的map 查找Channel//设置Channel的实际事件需要转换事件类型 int revents 0; if (events_[i].events EPOLLIN) { revents | Channel::kReadEvent; } if (events_[i].events EPOLLOUT) { revents | Channel::kWriteEvent; } // 错误事件转换 if (events_[i].events (EPOLLERR | EPOLLHUP)) { revents | Channel::kErrorEvent; } channel-set_revents(revents);作用 转换事件类型- EPOLLIN →Channel::kReadEvent- EPOLLOUT →Channel::kWriteEvent- EPOLLERR | EPOLLHUP →Channel::kErrorEvent为什么需要转换- Channel 使用自定义常量1、4、8- epoll 使用系统常量EPOLLIN 、EPOLLOUT 等- 转换可以解耦方便更换 IO 多路复用实现事件类型对照//加到活跃列表里 activeChannels-push_back(channel); } } }作用 将 Channel 添加到活跃列表- activeChannels 输出参数存储有事件的 Channel- EventLoop 会遍历这个列表调用每个 Channel 的handleEvent()//更新Channel void EpollPoller::updateChannel(Channel* channel) { int fd channel-fd(); int events channel-events(); struct epoll_event ev; memset(ev, 0, sizeof(ev)); ev.events events; ev.data.ptr channel;作用 准备epoll_event 结构- fd 从 Channel 获取文件描述符- events 从 Channel 获取关心的事件- memset(ev, 0, sizeof(ev)) 清零避免未初始化的字段- ev.events events 设置事件- ev.data.ptr channel 存储 Channel 指针//events_为0表示要删除 if (events 0) { auto it channels_.find(fd); if (it ! channels_.end()) { update(EPOLL_CTL_DEL, channel); channels_.erase(it); } return; }作用 处理删除操作- events 0 表示要删除 Channel- channels_.find(fd) 查找 fd 是否已注册- update(EPOLL_CTL_DEL, channel) 从 epoll 中删除- channels_.erase(it) 从 map 中删除为什么检查it ! channels_.end() - 避免删除不存在的 fd- epoll_ctl(EPOLL_CTL_DEL) 会失败auto it channels_.find(fd); if (it channels_.end()) { channels_[fd] channel; update(EPOLL_CTL_ADD, channel); } else if (it-second channel) { update(EPOLL_CTL_MOD, channel); } else { //fd复用旧channel没清理 channels_[fd] channel; update(EPOLL_CTL_ADD, channel); } }作用 处理添加或修改操作三种情况情况1fd 未注册- it channels_.end() fd 不在 map 中- channels_[fd] channel 添加到 map- update(EPOLL_CTL_ADD, channel) 添加到 epoll情况2fd 已注册且 Channel 相同- it-second channel map 中的 Channel 和当前 Channel 相同- update(EPOLL_CTL_MOD, channel) 修改 epoll 注册情况3fd 已注册但 Channel 不同fd 复用- it-second ! channel fd 被复用了- 可能的原因旧连接关闭时没有从 epoll 中删除- channels_[fd] channel 覆盖旧 Channel- update(EPOLL_CTL_ADD, channel) 重新添加到 epoll为什么会有 fd 复用- TCP 连接关闭后fd 会被回收- 新连接可能分配到相同的 fd- 如果旧连接没有正确清理就会出现这个问题//调用epoll_ctl void EpollPoller::update(int operation, Channel* channel) { int fd channel-fd(); struct epoll_event ev; memset(ev, 0, sizeof(ev)); ev.events channel-events(); ev.data.ptr channel; if (epoll_ctl(epollfd_, operation, fd, ev) 0) { printf(epoll_ctl failed! op%d fd%d\n, operation, fd); } }作用 调用epoll_ctl 系统调用- epollfd_ epoll 实例- operation 操作类型EPOLL_CTL_ADD 、EPOLL_CTL_MOD 、EPOLL_CTL_DEL - fd 文件描述符- ev 事件结构epoll_ctl 操作类型- EPOLL_CTL_ADD 添加 fd 到 epoll- EPOLL_CTL_MOD 修改 fd 的事件- EPOLL_CTL_DEL 从 epoll 中删除 fd错误处理- epoll_ctl 失败返回 -1- 可能的原因- EPOLL_CTL_ADD fd 已经注册- EPOLL_CTL_MOD fd 未注册- EPOLL_CTL_DEL fd 未注册
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门