480-手写C++muduo库(EPollPoller事件分发器,获取线程tid代码)

EPollPoller事件分发器

在这里插入图片描述
EPollPoller的主要实现:作为poller的派生类,把基类给派生类保留的这些纯虚函数的接口实现出来。
在这里插入图片描述
overirde表示在派生类里面,这些方法是覆盖方法。必须由编译器来保证在基类里面一定有这些函数的接口的声明。在派生类要重写他们。
给EPollPoller的析构函数写overide,就是让编译器给你检查基类的析构一定是虚函数。
底层是vector,放eventlist,可以动态地扩容。
成员变量的epollfd要通过epoll_create来创建,映射的就是epoll底层的文件系统
在这里插入图片描述

EPollPoller.h

#pragma once

#include "Poller.h"
#include "Timestamp.h"

#include <vector>
#include <sys/epoll.h>

class Channel;

/**
 * epoll的使用  
 * epoll_create  创建fd
 * epoll_ctl     进行add/mod/del
 * epoll_wait    
 */ 
 
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_ctrl的行为
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的第二个参数 
};

在这里插入图片描述
epoll_create的参数size没有意义了,但是必须是大于0的数
epoll_create1,底下通过行为选项
在这里插入图片描述
在这里插入图片描述
当我们去使用epoll_create1的时候,创建的epollfd,然后在当前线程里面再去fork创建一个子进程,然后用exec替换子进程,在子进程里面就把父进程设置成标志的fd,资源就都给关闭了。
在这里插入图片描述
结构体的fd就是epoll要监听的事件,ptr就是针对fd携带的数据,相当于是fd对于的channel
在这里插入图片描述
在这里插入图片描述

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输出日志更为合理,可以设置开启或者不开启 
    LOG_INFO("func=%s => fd total count:%lu \n", __FUNCTION__, channels_.size());
    //对poll的执行效率有所影响 
    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 = saveErrno;//适配 ,把errno重置成当前loop之前发生的错误的值 
            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 删掉 
    }
    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);
        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("epoll_ctl del error:%d\n", errno);
        }
        else//添加或者更改错误,这个会自动exit 
        {
            LOG_FATAL("epoll_ctl add/mod error:%d\n", errno);
        }
    }
}

获取线程tid代码

我们的服务器程序不一定就只有1个eventloop,我们可能有很多的eventloop,每个eventloop都有很多channel,自己channel上的事件要在自己的eventloop线程上去处理,eventloop在这里涉及到获取当前线程的ID
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看到是全局变量, 所有线程共享的,加了__thread,或者是C++11的thread_local来定义的话,就是:虽然是全局变量,但是会在每一个线程存储一份拷贝,这个线程对这个变量的更改,别的线程是看不到的。
第一次访问,就把当前线程的Tid存储起来,后边如果再访问就从缓存取。
在这里插入图片描述
在这里插入图片描述
其实就是用了一个系统调用获取当前线程的ID值。

CurrentThread.h

#pragma once

#include <unistd.h>
#include <sys/syscall.h>

namespace CurrentThread
{
    extern __thread int t_cachedTid;

    void cacheTid();

    inline int tid()//内联函数,在当前文件起作用 
    {
        if (__builtin_expect(t_cachedTid == 0, 0))//还没有获取过当前线程的id 
        {
            cacheTid();
        }
        return t_cachedTid;
    }
}

CurrentThread.cc

#include "CurrentThread.h"

namespace CurrentThread
{
    __thread int t_cachedTid = 0;   

    void cacheTid()
    {
        if (t_cachedTid == 0)
        {
            //通过linux系统调用,获取当前线程的tid值
            t_cachedTid = static_cast<pid_t>(::syscall(SYS_gettid));
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值