本文最后更新于:8 个月前
WebServer(3) Poller类
写在前面
源码链接:https://github.com/yxiao-yang/Tiny-Webserver.git
正文
1 基类Poller的设计
我们编写网络编程代码的时候少不了使用IO复用系列函数,而 muduo 也为我们提供了对此的封装。 muduo 有 Poller 和 EPollPoller 类分别对应着epoll
和poll
。而我们使用的接口是Poller
, muduo 以 Poller 为虚基类,派生出 Poller 和 EPollPoller 两个子类,用不同的形式实现 IO 复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Poller : noncopyable { public: using ChannelList = std::vector<Channel*>;
Poller(EventLoop *Loop); virtual ~Poller() = default;
virtual Timestamp Poll(int timeoutMs, ChannelList *activeChannels) = 0; virtual void UpdateChannel(Channel *channel) = 0; virtual void RemoveChannel(Channel *channel) = 0;
bool HasChannel(Channel *channel) const;
static Poller* NewDefaultPoller(EventLoop *Loop);
protected: using ChannelMap = std::unordered_map<int, Channel*>; ChannelMap channels_;
private: EventLoop *ownerLoop_; };
|
ChannelMap
channels_
需要存储从 fd -> channel 的映射
ownerLoop_
定义 Poller 所属的事件循环 EventLoop
重写方法靠派生类实现,这里我们可以专注一下 NewDefaultPoller
方法。
在 muduo 中可以使用此方法获取不同的实例,并且这个方法是在单独的一个 DefaultPoller.cc
文件内实现的。正常情况下,我们可能会在 Poller.cc
文件中完成该成员函数的实现。但是这并不是一个好的设计,因为 Poller 是一个基类。如果在 Poller.cc
文件内实现则势必会在 Poller.cc
包含 EPollPoller.h
等头文件。在一个基类中包含其派生类的头文件,这个设计可以说是很诡异的,这并不是一个好的抽象。
因此,我们专门设置了另一个DefaultPoller.cc
文件,在其中包含了 Poller.h
和 EPollPoller.h
的头文件。这样就让 Poller.h
文件显得正常了。
1 2 3 4 5 6 7
| Poller* Poller::NewDefaultPoller(EventLoop *loop) { if (::getenv("MUDUO_USE_POLL")) { return nullptr; } else { return new EPollPoller(loop); } }
|
2 EPollPoller类设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class EPollPoller : public Poller { using EventList = std::vector<epoll_event>; public: EPollPoller(EventLoop *Loop); ~EPollPoller() override;
Timestamp Poll(int timeoutMs, ChannelList *activeChannels) override; void UpdateChannel(Channel *channel) override; void RemoveChannel(Channel *channel) override;
private: static const int kInitEventListSize = 16;
void FillActiveChannels(int numEvents, ChannelList *activeChannels) const;
void Update(int operation, Channel *channel);
int epollfd_; EventList events_; };
|
kInitEventListSize
默认监听事件的数量
epollfd_
我们使用 epoll_create 创建的指向 epoll 对象的文件描述符(句柄)
EventList events_
返回事件的数组
可以看到,EPollPoller类继承了Poller类,并打算重写这些核心方法。
3 成员函数
3.1 返回发生事件的 poll 方法
该方法内部调用 epoll_wait 获取发生的事件,并找到这些事件对应的 Channel 并将这些活跃的 Channel 填充入 activeChannels 中,最后返回一个时间戳。
通过 numEvents
的值判断事件情况
numEvents > 0
事件发生,需要调用 FillActiveChannels
填充活跃的 Channel。
numEvents == 0
事件超时了,打印日志。(可以设置定时器操作)
- 其他情况则是出错,打印
LOG_ERROR
日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| Timestamp EPollPoller::Poll(int timeoutMs, ChannelList *activeChannels) { size_t numEvents = ::epoll_wait(epollfd_, &(*events_.begin()), static_cast<int>(events_.size()), timeoutMs); int saveErrno = errno; Timestamp now(Timestamp::now());
if (numEvents > 0) { FillActiveChannels(numEvents, activeChannels); if (numEvents == events_.size()) { events_.resize(events_.size() * 2); } } else if (numEvents == 0) { LOG_DEBUG << "timeout!"; } else { if (saveErrno != EINTR) { errno = saveErrno; LOG_ERROR << "EPollPoller::poll() failed"; } } return now; }
|
3.2 填写活跃的连接 FillActiveChannels
通过 epoll_wait 返回的 events 数组内部有指向 channel 的指针,我们可以通过此指针在 EPollPoller 模块获取对 channel 进行操作。
我们需要更新 channel 的返回事件的设置,并且将此 channel 装入 activeChannels。
1 2 3 4 5 6 7 8
| void EPollPoller::FillActiveChannels(int numEvents, ChannelList *activeChannels) const { for (int i = 0; i < numEvents; ++i) { Channel *channel = static_cast<Channel*>(events_[i].data.ptr); channel->Set_revents(events_[i].events); activeChannels->push_back(channel); } }
|
3.3 更新channel在epoll上的状态
我们获取 channel 在 EPollPoller 上的状态,根据状态进行不同操作。最后调用 Update 私有方法。
- 如果此 channel 还没有被添加到 epoll 上或者是之前已经从 epoll 上注销
index == kNew || index == kDeleted
,那么此 channel 接下来会进行添加操作。
- 如果是未添加状态
kNew
,则需要在 map 上增加此 channel
- 如果是已删除状态
kDeleted
,则直接往后执行
- 设置 channel 状态为
kAdded
,然后调用 Update(EPOLL_CTL_ADD, channel);
- 如果已经在 poller 上注册的状态,则要进行删除或修改操作,需要判断此 channel 是否还有监视的事情(是否还要事件要等着处理)
- 如果没有则直接删除,调用
Update(EPOLL_CTL_DEL, channel);
并重新设置状态为 kDeleted
- 如果还有要监视的事情,则说明要进行修改(MOD)操作,调用
update(EPOLL_CTL_MOD, channel);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void EPollPoller::UpdateChannel(Channel *channel) { const int index = channel->index();
if (index == kNew || index == kDeleted) { if (index == kNew) { int fd = channel->fd(); channels_[fd] = channel; } else { } channel->set_index(kAdded); Update(EPOLL_CTL_ADD, channel); } else { if (channel->IsNoneEvent()) { Update(EPOLL_CTL_DEL, channel); channel->set_index(kDeleted); } else { Update(EPOLL_CTL_MOD, channel); } } }
|
接着往下看 Update 操作,其本质是调用 epoll_ctl
函数,而里面的操作由之前的 UpdateChannel
所指定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void EPollPoller::Update(int operation, Channel *channel) { epoll_event event; ::memset(&event, 0, sizeof(event)); int fd = channel->fd(); event.events = channel->events(); event.data.fd = fd; event.data.ptr = channel;
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) { if (operation == EPOLL_CTL_DEL) { LOG_ERROR << "epoll_ctl() del error:" << errno; } else { LOG_FATAL << "epoll_ctl add/mod error:" << errno; } } }
|
3.4 从 epoll 中移除监视的channel
1 2 3 4 5 6 7 8 9 10 11 12 13
| void EPollPoller::RemoveChannel(Channel *channel) { int fd = channel->fd(); channels_.erase(fd);
int index = channel->index(); if (index == kAdded) { Update(EPOLL_CTL_DEL, channel); }
channel->set_index(kNew); }
|
- 断言此
channel
已经没有可关注的事件了。
- 从
unordered_map<fd, Channel*> channels
中删除此channel
,根据fd
删除。
- 如果之前
channel
的状态是添加状态,则还需调用Update
方法,将此channel
从epoll
对象中删除。
channel
设置其状态为未被监视状态。