muduo网络库(六):Poller类与IO复用
- muduo网络库(六):Poller类与IO复用
- 概述
- EpollPoller 子类
- 核心方法
- Channel 与 epoll_event 的绑定机制
- newDefaultPoller 为什么单独放在一个文件中
- 精髓总结
muduo网络库(六):Poller类与IO复用
概述
Poller是 muduo 网络库中IO 复用机制的抽象层,它封装了不同的多路复用技术(如 epoll、poll、select),为上层提供统一的接口。EpollPoller是其基于 Linux epoll 实现的具体子类。
在Poller类中只有两个核心成员:
- 一个
channels_Map,存储当前所有的 Channel - 一个
loop_指针,标识当前所属的 EventLoop
其余都是纯虚函数,因为每种具体的多路复用技术(epoll、poll 等)都有各自的实现逻辑。
- 一个
EpollPoller 子类
EpollPoller是对 epoll 的封装,因此有两个关键成员:
epollFd_:epoll 的监听文件描述符,作为"监听仓库"events_:vector<epoll_event>,保存 epoll_wait 返回的事件容器
核心方法
1. updateChannel()
先把新 Channel 加入到channels_Map 中(删除则修改 index),然后根据 Channel 的状态调用 epoll 的update函数(即epoll_ctl)。
状态转换逻辑如下:
kNew(未注册)→ 加入 channels Map → 变为kAddedkAdded(已注册)→ 如果无感兴趣事件(isNoneEvent())→EPOLL_CTL_DEL→ 变为kDeleted
kAdded→ 如果有事件变更 →EPOLL_CTL_MOD
kDeleted(已删除)→ 重新添加 →EPOLL_CTL_ADD→ 变为kAdded
通过 Channel 的index_成员来标识它在 epoll 中的状态,从而决定是添加、修改还是删除。
2. removeChannel()
从channels_Map 中删除指定的 Channel,同时从 epoll 中删除。
3. poll()
调用epoll_wait()函数监听所有已注册的 fd。要监听的 fd 已经通过updateChannel加入到epollFd_中了。返回结果存储在events_中(必要时会动态扩容)。
4. fillActiveChannels()
遍历events_,由于epoll_event结构中的data.fd和data.ptr指向的 Channel 都是绑定的,直接将data.ptr转换为Channel*加入到activeChannels中即可。后续在EventLoop::loop()中遍历activeChannels,调用Channel::handleEvent()处理事件。
注意:Poller 只是监听 fd,当监听到可读事件后,上层调用
accept()才真正建立连接。Poller 相当于只负责监听当前线程的 fd,构建 channels,是 EventLoop 的一部分。
Channel 与 epoll_event 的绑定机制
Poller结合EpollPoller将 epoll 机制封装,每个 EventLoop 只有一个 Poller,用于监听所有 Channel。核心在于利用epoll_event.data.ptr将 Channel 和 epoll 监听的事件绑定起来。
注册时绑定:
structepoll_eventevent;event.events=channel->events();event.data.ptr=channel;// 将 Channel 指针存入 epoll_event::epoll_ctl(epollFd_,operation,fd,&event);事件触发时取回:
Channel*channel=static_cast<Channel*>(events_[i].data.ptr);这样就将事件 Channel 与 IO 多路复用机制联系了起来,而它们都在 EventLoop 类中协作。
newDefaultPoller 为什么单独放在一个文件中
DefaultPoller::newDefaultPoller()是一个工厂方法,用于:
- 动态选择 IO 复用实现:根据环境变量
MUDUO_USE_POLL决定使用哪种 IO 复用方式 - 解耦架构:EventLoop 不需要直接依赖具体的 epoll 或 poll 实现,通过工厂方法获取。本项目中只实现了 epoll,所以默认返回
EpollPoller
这体现了依赖倒置原则:抽象不依赖于具体,具体依赖于抽象。单独放在一个文件中的关键原因是避免循环依赖:
- 解耦架构:EventLoop 不需要直接依赖具体的 epoll 或 poll 实现,通过工厂方法获取。本项目中只实现了 epoll,所以默认返回
如果把newDefaultPoller放在Poller.cc中实现,Poller.cc需要包含具体子类的头文件#include "EPollPoller.h",而EPollPoller.h又需要继承Poller,包含#include "Poller.h",这就形成了循环依赖,导致编译失败。
Poller.h (抽象基类) ↑ 继承 EPollPoller.h DefaultPoller.cc (工厂方法) → 包含 Poller.h 和 EPollPoller.h → 负责创建具体实例将工厂方法单独放在DefaultPoller.cc中,打破了这种循环依赖,让架构更加清晰。
精髓总结
Poller 类的设计体现了几个关键思想:
- 多态抽象:
Poller定义统一接口,EpollPoller提供具体实现,上层代码不感知底层细节 - epoll_event.data.ptr 绑定:巧妙利用 epoll 的
data.ptr字段,将事件和 Channel 直接关联,零开销取回
- epoll_event.data.ptr 绑定:巧妙利用 epoll 的
- 工厂模式解耦:
newDefaultPoller单独编译,避免循环依赖,遵循依赖倒置原则
- 工厂模式解耦:
- 每个 EventLoop 一个 Poller:符合 One Loop Per Thread 模型,线程内无需加锁