目录
EPollPoller::fillActiveChannels
Epoll的使用流程:
1. 创建epoll实例,调用epoll_create或epoll_create1函数创建一个epoll实例,返回一个文件描述符,用于后续对epoll进行操作;
2.注册事件:使用epoll_ctl函数注册需要监听的文件描述符,及其关注的事件类型,将其添加到epoll实例中;可以使用 EPOLL_CTL_ADD 添加新的文件描述符,或使用 EPOLL_CTL_MOD 修改已注册的文件描述符的事件类型,或使用 EPOLL_CTL_DEL 删除已注册的文件描述符。
3.进入事件循环:等待事件发生,使用epoll_wait函数在epoll实例上进行阻塞等待,直到有事件发生;
4.处理事件:当epoll_wait返回后,表示有事件发生。遍历返回的事件列表,根据事件类型进行相应的处理,如读数据、发送数据、关闭连接等。
5.循环回到步骤3,等待下一次事件的发生;
6.退出事件循环:当满足退出条件时,跳出事件循环,释放相关资源,关闭epoll实例。
EpollPoller对应epoll关系:
* epoll_create 创建fd, 在EPollPoller的构造函数和析构函数中
* epoll_ctl 进行add/mod/del,在updateChannel和removeChannel函数中
* epoll_wait 在poll函数中
EPollPoller成员变量:

EpollPoller底层其实就是一个epoll的实现,这里声明了epollfd,和epoll_event容器,后面封装epoll的时候用。
EPollPoller成员函数:
构造函数、析构函数:

①:在EpollPoller的构造函数的参数化列表中声明一下其基类的构造函数,可以确保在创建EpollPoller对象时,先调用Poller的构造函数进行初始化,然后再进行EpollPoller的自身初始化。

②:创建epoll实例:
epoll_create1(int flags)
EPOLL CLOEXEC:
当我们去使用epol create1的时候,创建的epollfd,然后在当前进程里面再去fork创建一个子进程,然后用exec替换子进程,在子进程里面就把父进程设置了标志的fd,资源就都给关闭了:
作用是为了解决使用 fork 函数创建子程序后,在子程序中执行 exec 函数族时自动关闭无用的文件描述符.(默认情况,子进程会继承父进程所打开的所有fd资源)
设置这个标志目的在于,在
exec调用时自动关闭该文件描述符。
Channel这里边的update跟remove,最终调用了poller中的的updatechannel跟removechannel来把channel本身从poller里边添加修改或者是删除掉。因为channel是无法直接访问poller的,它们俩没有这个从属关系对吧?但是它们俩都是属于eventloop来管理的。
Channel的update跟remove最终调用的都是他的父组件eventloop,通过eventloop来调用相应的方法来访问poller相应的方法。

EPollPoller::updateChannel
标识Channel在poller中的状态:


①:获取Channel的index,默认在Channel的构造函数中设置为-1;
②:如果该Channel是未添加、被删除了

未添加的需要将其放入到存放Channel的map中。
然后将index设置为kAdde,接着调用update(EPOLL_CTL_ADD, channel)处理事件。
③: 已经注册过了,进一步判断channel的状态,如果是events_ == kNoneEvent(对所有事件都感兴趣)状态调用update(EPOLL_CTL_DEL, channel),并将channel在poller中index=kDeleted
④:调用 update(EPOLL_CTL_MOD, channel)。
EPollPoller::removeChannel

EPollPoller::update

①:构建 epoll_event结构体;
②:调用 epoll_ctl去执行具体操作。
EPollPoller::poll
eventloop会创建一个channelist,并把创建好的channelist的地址传给poll,poll通过epoll_wait监听到哪些fd发生了事件,把真真正正发生事件的channel通过形参发送到eventloop提供的实参里面。

①:调用epoll_wait阻塞等待事件发生,返回事件发生的数量,超时事件默认10秒;
②:把活跃的连接添加到ChannelList中去。
EPollPoller::fillActiveChannels

EpollPoller.h
#pragma once
#include "Poller.h"
#include "Timestamp.h"
#include <vector>
#include <sys/epoll.h>
class Channel;
/**
* epoll的使用
* epoll_create 创建fd, 在EPollPoller的构造函数和析构函数中
* epoll_ctl 进行add/mod/del,在updateChannel和removeChannel函数中
* epoll_wait 在poll函数中
*/
class EPollPoller : public Poller
{
public:
EPollPoller(EventLoop *loop);
~EPollPoller() override;
//重写基类Poller的抽象方法
Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;
void updateChannel(Channel *channel) override;
void removeChannel(Channel *channel) override;
//上面两个方法表示epoll_ctl的行为
private:
static const int kInitEventListSize = 16;//初始化vector长度
//填写活跃的连接
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
//更新channel通道
void update(int operation, Channel *channel);
using EventList = std::vector<epoll_event>;
int epollfd_;
EventList events_;//epoll_wait的第二个参数
};
EpollPoller.cc
#include "EPollPoller.h"
#include "Logger.h"
#include "Channel.h"
#include <errno.h>
#include <unistd.h>
#include <strings.h>
//标识channel和epoll的状态
//channel未添加到poller中
const int kNew = -1;//channel的成员index_ = -1
//channel已添加到poller中
const int kAdded = 1;
//channel从poller中删除
const int kDeleted = 2;
EPollPoller::EPollPoller(EventLoop *loop)//构造函数
: Poller(loop)
, epollfd_(::epoll_create1(EPOLL_CLOEXEC))
, events_(kInitEventListSize)//创建vector<epoll_event> //默认的长度是16
{
if (epollfd_ < 0)
{
LOG_FATAL("epoll_create error:%d \n", errno);
}
}
EPollPoller::~EPollPoller()//析构函数
{
::close(epollfd_);
}
//epoll_wait
//eventloop会创建一个channellist,并把创建好的channellist的地址传给poll
//poll通过epoll_wait监听到哪些fd发生了事件,把真真正正发生事件的channel通过形参发送到eventloop提供的实参里面
Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
//实际上应该用LOG_DEBUG输出日志更为合理,可以设置开启或者不开启
//对poll的执行效率有所影响
LOG_INFO("func=%s => fd total count:%lu \n", __FUNCTION__, channels_.size());
//灵活使用vector可以动态扩容
//&*events_.begin() : 首先通过begin获取events_中第一个元素的迭代器,然后通过 * 解引用获取该迭代器指向的元素,然后再& 取地址
int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
//events_.begin()返回首元素的迭代器(数组),也就是首元素的地址,是面向对象的,要解引用,就是首元素的值,然后取地址
//就是vector底层数组的起始地址 static_cast类型安全的转换 timeoutMs超时时间
int saveErrno = errno;//全局的变量errno,库里的,poll可能在多个线程eventloop被调用 ,所以有局部变量存起来
Timestamp now(Timestamp::now());//获取当前时间
if (numEvents > 0)//表示有已经发生相应事件的个数
{
LOG_INFO("%d events happened \n", numEvents);
fillActiveChannels(numEvents, activeChannels);
if (numEvents == events_.size())//所有的监听的event都发生事件了,得扩容了
{
events_.resize(events_.size() * 2);//扩容
}
}
else if (numEvents == 0)//epoll_wait这一轮监听没有事件发生,timeout超时了
{
LOG_DEBUG("%s timeout! \n", __FUNCTION__);
}
else
{
if (saveErrno != EINTR)//不等于外部的中断 ,是由其他错误类型引起的
{
/*
适配,把errno重置成当前loop之前发生的错误的值(其他loop可能会发生错误,将全局的errno修改了)
*/
errno = saveErrno;
LOG_ERROR("EPollPoller::poll() err!");
}
}
return now;
}
// channel update remove => EventLoop updateChannel removeChannel => Poller updateChannel removeChannel
/**
* EventLoop => poller.poll
* ChannelList Poller
* ChannelMap <fd, channel*> epollfd
*/
void EPollPoller::updateChannel(Channel *channel)
{
const int index = channel->index();
LOG_INFO("func=%s => fd=%d events=%d index=%d \n", __FUNCTION__, channel->fd(), channel->events(), index);
if (index == kNew || index == kDeleted)//未添加或者已删除
{
if (index == kNew)//未添加,键值对写入map中
{
int fd = channel->fd();
channels_[fd] = channel;
}
channel->set_index(kAdded);
update(EPOLL_CTL_ADD, channel);//相当于调用epoll_ctr,添加1个channel到epoll中
}
else//channel已经在poller上注册过了
{
int fd = channel->fd();
if (channel->isNoneEvent())//已经对任何事件不感兴趣,不需要poller帮忙监听了
{
update(EPOLL_CTL_DEL, channel);//删除已注册的channel的感兴趣的事件
channel->set_index(kDeleted);//删掉
}
else//包含了fd的事件,感兴趣
{
update(EPOLL_CTL_MOD, channel);
}
}
}
//从poller中删除channel
void EPollPoller::removeChannel(Channel *channel)
{
int fd = channel->fd();
channels_.erase(fd);//从map中删掉
LOG_INFO("func=%s => fd=%d\n", __FUNCTION__, fd);
int index = channel->index();
if (index == kAdded)//如果已注册过
{
update(EPOLL_CTL_DEL, channel);//通过epoll_ctrl 从epoll中也要删掉
}
channel->set_index(kNew);//设置成未添加的状态
}
//填写活跃的连接
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); //设置当前发生事件的 revents
activeChannels->push_back(channel);//EventLoop就拿到了它的poller给它返回的所有发生事件的channel列表了
//至于EventLoop拿到这些channel干什么事情,我们看 EventLoop的代码
}
}
//更新channel通道 就是epoll_ctl 的 add/mod/del 操作
void EPollPoller::update(int operation, Channel *channel)
{
epoll_event event;
bzero(&event, sizeof event);
int fd = channel->fd();
event.events = channel->events();//返回的就是fd所感兴趣的事件
event.data.fd = fd;
event.data.ptr = channel;//绑定的参数
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)//把fd相关事件更改
{
//出错了
if (operation == EPOLL_CTL_DEL)//没有删掉,我们可以不用,影响不大,LOG_ERROR输出即可
{
LOG_ERROR("epoll_ctl del error:%d\n", errno);
}
else//add添加或者mod更改错误,是LOG_FATAL(不可容忍的错误),这个会自动exit
{
LOG_FATAL("epoll_ctl add/mod error:%d\n", errno);
}
}
}
文章详细阐述了epoll在Linux中的使用流程,包括创建epoll实例、注册事件、事件循环以及处理事件。EPollPoller作为epoll的封装,其成员变量和函数如updateChannel、removeChannel和poll等,都在文中进行了深入解析,展示了如何在C++中实现事件驱动的网络编程。
5224

被折叠的 条评论
为什么被折叠?



