muduo 15 EpollPoller

文章详细阐述了epoll在Linux中的使用流程,包括创建epoll实例、注册事件、事件循环以及处理事件。EPollPoller作为epoll的封装,其成员变量和函数如updateChannel、removeChannel和poll等,都在文中进行了深入解析,展示了如何在C++中实现事件驱动的网络编程。

目录

Epoll的使用流程:

EpollPoller对应epoll关系:

EPollPoller成员变量:

EPollPoller成员函数:

构造函数、析构函数:

EPollPoller::updateChannel

EPollPoller::removeChannel

EPollPoller::update      

EPollPoller::poll

EPollPoller::fillActiveChannels

EpollPoller.h

EpollPoller.cc


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);
        }
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值