Poller 是对 Linux IO 多路复用(epoll)的封装抽象类。1 个 Poller 管理 多个 Channel,事件来了由 Poller 通知 Channel 处理,Poller = 监工,负责监听所有 fd 的事件(底层是 epoll)

poller 是抽象基类,不能实例化,定义统一接口:pollupdateremove,子类 EPollPoller 真正实现 epoll 操作。

Poller.h

1
2
3
4
5
6
7
8
9
10
#pragma once

#include "noncopyable.h"
#include "Timestamp.h"

#include <vector>
#include <unordered_map>

class EventLoop;
class Channel;
1
2
3
4
5
class Poller : noncopyable
{
private:
EventLoop* ownerLoop_;
};

Poller属于哪个EventLoop。

1
2
3
protected:
using ChannelMap = std::unordered_map<int, Channel*>;
ChannelMap channels_;
  • channels_:保存所有被监听的 channel,通过fd找到对应的channel

  • protected权限,使子类(EpollPoller可以调用。

  • unordered_map不用map,因为在不要求有序,不要求遍历排序(map提供有序功能),unordered_map性能更高。

1
2
Poller(EventLoop* loop);
virtual ~Poller() = default;

virtual 虚析构多态安全,保证子类析构被执行,在子类析构中释放对应资源,防止资源泄漏。

1
Poller* poller = new EpollPoller(loop);

后续会像如上用父类指针指向子类对象

  • 如果析构不是virtual那么delete poller 时:
    • 编译器看到的是 Poller* 指针
    • 它只会调用 Poller 的析构函数
    • 完全不会调用 EpollPoller 的析构函数
  • virtual后再执行delete poller时:
    • 因为是 虚函数,编译器不会直接调用 Poller 的析构
    • 它去查虚表,找到真实对象(EpollPoller)的析构
    • 先调用 子类析构~EpollPoller ()
    • 再自动调用 父类析构~Poller ()
1
2
3
4
5
using ChannelList = std::vector<Channel*>;

virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0;
virtual void updateChannel(Channel* channel) = 0; //添加/修改 监听
virtual void removeChannel(Channel* channel) = 0; //彻底删除监听
  • 三个纯虚函数,是poller全部功能,在子类中实现。

  • poll底层对应 epoll_wait, 把有事件的 Channel 填进传入的ChannelList里。

  • updateChannel 中的删除(DEL)只是临时取消监听,但 Channel 还在 Poller 里,只是从 epoll 中移除监听,channels_ 这个 map 里还保留着 channel。

  • removeChannel 中的删除是彻底从 Poller 中剥离 Channel,从 epoll 里删掉,从 channels_ map 里也删掉。

1
bool hasChannel(Channel* channel) const;

判断 Channel 是否被当前 Poller 管理

1
static Poller* newDefaultPoller(EventLoop* loop);

静态工厂方法,创建并返回一个 Poller 的具体实现(如 EpollPoller)

  • 返回基类指针

  • 实际指向子类对象(EpollPoller)

  • 这就是 多态

Poller.cc

1
2
3
4
5
6
7
#include "Poller.h"
#include "Channel.h"

Poller::Poller(EventLoop* loop) : ownerLoop_(loop)
{

}

构造函数,成员初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool Poller::hasChannel(Channel* channel) const
{
auto it = channels_.find(channel->fd());
return it != channels_.end() && it->second == channel;#include "Poller.h"
#include "EpollPoller.h"

Poller* Poller::newDefaultPoller(EventLoop* loop)
{
if(::getenv("MUDUO_USE_POLL"))
{
return nullptr; //生成poll的实例
}
else
{
return new EpollPoller(loop); //生成epoll实例
}
}


}
  • channel->fd()拿到这个 Channel 对应的文件描述符。find(...)在 map 里查找这个 fd
  • 双重安全判断
    • it != channels_.end()fd 在 map 里存在。
    • it->second == channelmap 里存的 Channel 指针 == 传进来的这个 Channel。

为什么要判断两个条件?

因为 fd 会被系统重复利用!

例子:

  1. 旧 Channel(fd=5)关闭
  2. 系统把 fd=5 又分配给新 Channel
  3. 这时候 find(5) 能找到,但对应的已经不是原来的 Channel

DefualtPoller.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "Poller.h"
#include "EpollPoller.h"

Poller* Poller::newDefaultPoller(EventLoop* loop)
{
if(::getenv("MUDUO_USE_POLL"))
{
return nullptr; //生成poll的实例
}
else
{
return new EpollPoller(loop); //生成epoll实例
}
}

新建DefaultPoller.cc文件,在这个文件里实现Poller中的newDefaultPoller函数。

这是简单工厂模式

  • EventLoop 只知道 Poller,不知道 EpollPoller
  • 解耦,以后想换 IO 模型,只改这里,不用动 EventLoop

为什么 newDefaultPoller 非要单独写一个文件?

  • Poller 是抽象接口,它不应该、也不能知道EpollPoller 这个子类。

  • newDefaultPoller () 必须知道 EpollPoller,因为它要 new EpollPoller

  • 这两个需求冲突,所以必须分开文件

源码地址:

Poller.h:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/Poller.h

DefaultPoller.cc:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/DefaultPoller.cc