muduo源码-EpollPoller.h

0 设计

EpollPoller继承自Poller,为的是能够使用epoll和poll两种io函数。(这貌似是策略模式?记不清了)

1 源码

EpollPoller.h

#ifndef MUDUO_NET_POLLER_EPOLLPOLLER_H
#define MUDUO_NET_POLLER_EPOLLPOLLER_H

#include <muduo/net/Poller.h>

#include <vector>

struct epoll_event;

namespace muduo
{
namespace net
{

// epoll 继承自poller ,主要是为了能够同时使用epoll和poll
// 这里应该算是策略模式吧
class EPollPoller : public Poller
{
  public:
    EPollPoller(EventLoop *loop);
    virtual ~EPollPoller();

    virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels);
    virtual void updateChannel(Channel *channel);
    virtual void removeChannel(Channel *channel);

  private:
    static const int kInitEventListSize = 16;

    static const char *operationToString(int op);

    void fillActiveChannels(int numEvents,
                            ChannelList *activeChannels) const;
    void update(int operation, Channel *channel);

    int epollfd_;

    // 这个将会代替 epoll_event 的数组,使用begin()迭代器传入 wait
    // 是用来存储已经就绪的 epoll_event 的
    typedef std::vector<struct epoll_event> EventList;
    EventList events_;

    //这里是有 channels_ 成员的 是一个map,从基类中继承而来
};
}
}
#endif // MUDUO_NET_POLLER_EPOLLPOLLER_H

EpollPoller.cc

#include <muduo/net/poller/EPollPoller.h>

#include <muduo/base/Logging.h>
#include <muduo/net/Channel.h>

#include <boost/static_assert.hpp>

#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <sys/epoll.h>
#include <unistd.h>

using namespace muduo;
using namespace muduo::net;

// boost中的BOOST_STATIC_ASSERT是在编译期的断言,也就是说在编译的时候就可以断言出错误。
// 主要是兼容
BOOST_STATIC_ASSERT(EPOLLIN == POLLIN);
BOOST_STATIC_ASSERT(EPOLLPRI == POLLPRI);
BOOST_STATIC_ASSERT(EPOLLOUT == POLLOUT);
BOOST_STATIC_ASSERT(EPOLLRDHUP == POLLRDHUP);
BOOST_STATIC_ASSERT(EPOLLERR == POLLERR);
BOOST_STATIC_ASSERT(EPOLLHUP == POLLHUP);

namespace
{

// 又是匿名空间
// 这三个值其实是 标识符,用来识别,一个channel 是新添加,还是待修改,还是之前添加过
// 其含义是是否在epoll 中被监听
const int kNew = -1;
const int kAdded = 1;
const int kDeleted = 2;
}

EPollPoller::EPollPoller(EventLoop *loop)
    : Poller(loop),
      epollfd_(::epoll_create1(EPOLL_CLOEXEC)),
      events_(kInitEventListSize)
{
    if (epollfd_ < 0)
    {
        LOG_SYSFATAL << "EPollPoller::EPollPoller";
    }
}

EPollPoller::~EPollPoller()
{
    ::close(epollfd_);
}

// 实际的wait函数
// 第一个参数是超时时间,第二个参数是:所有已就绪的channel被填充到activeChannels
// 超时时间 : 0会立即返回,-1将不确定,也有说法说是永久阻塞
Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
    LOG_TRACE << "fd total count " << channels_.size();

    // 注意这里, epoll_event 结构体使用的是vector,传入的begin()
    int numEvents = ::epoll_wait(epollfd_,
                                 &*events_.begin(),
                                 static_cast<int>(events_.size()),
                                 timeoutMs);
    int savedErrno = errno;
    // 记录唤醒的时间
    Timestamp now(Timestamp::now());
    // 通过 epoll_wait 返回值来判断是否有文件描述符就绪
    if (numEvents > 0)
    {
        // 当确实有就绪的时候,那么就开始 fillActiveChannels 填充到 activeChannels
        LOG_TRACE << numEvents << " events happened";
        fillActiveChannels(numEvents, activeChannels);

        // 然后当传入的events 的空间太小的时候,也就是说,还有很多就绪的时间没有被传出来
        // 那么就扩容events
        if (implicit_cast<size_t>(numEvents) == events_.size())
        {
            events_.resize(events_.size() * 2);
        }
    }
    else if (numEvents == 0)
    {
        // 超时以后,没有返回
        LOG_TRACE << "nothing happened";
    }
    else
    {
        // 被信号中断
        if (savedErrno != EINTR)
        {
            errno = savedErrno;
            LOG_SYSERR << "EPollPoller::poll()";
        }
    }
    return now;
}

// 填充函数
void EPollPoller::fillActiveChannels(int numEvents,
                                     ChannelList *activeChannels) const
{
    assert(implicit_cast<size_t>(numEvents) <= events_.size());

    // 遍历每一个就绪的 epoll_event
    // 要知道 epoll_event 的 data.ptr 指针绑定的是文件描述符被封装的 channel
    for (int i = 0; i < numEvents; ++i)
    {
        // 这里直接通过 data.ptr 来获得channel即可
        Channel *channel = static_cast<Channel *>(events_[i].data.ptr);

        // 下面的四句是用来判断channel 的正确性的
#ifndef NDEBUG
        int fd = channel->fd();
        ChannelMap::const_iterator it = channels_.find(fd);
        assert(it != channels_.end());
        assert(it->second == channel);
#endif
        // channel 内设置发生的事件,他就等于  epoll_event 的events
        channel->set_revents(events_[i].events);
        activeChannels->push_back(channel);
    }
}

// 更新channel 。由channel 发起,被eventloop 转发
void EPollPoller::updateChannel(Channel *channel)
{
    Poller::assertInLoopThread();
    // index 是 channel 的状态,是新添加,还是已经在监听,还是之前间听过,后来移除监听了
    const int index = channel->index();
    LOG_TRACE << "fd = " << channel->fd()
              << " events = " << channel->events() << " index = " << index;

    //   当是新的或是之前监听过,后来移除了监听
    // 两者的区别在于,新的channel 之前没有在epoll 中保存
    // 而 del 的之前在 channels_ 中保存了,但是没有被放入epoll_ctl中监听
    if (index == kNew || index == kDeleted)
    {

        int fd = channel->fd();
        if (index == kNew)
        {
            assert(channels_.find(fd) == channels_.end());
            // channels_ 是一个map 在应用层,保存了所有的 channel 指针,维护一套channel
            channels_[fd] = channel;
        }
        else // index == kDeleted
        {
            // 如果index 是del ,那么 channels_ 中有channel 的记录
            assert(channels_.find(fd) != channels_.end());
            assert(channels_[fd] == channel);
        }
        // 然后都是设置 index 表示已添加到 epoll 中
        channel->set_index(kAdded);
        // 更新个 epoll 去调用 epoll ctl
        update(EPOLL_CTL_ADD, channel);
    }
    else
    {
        // 如果是已经add 了。那么应该是要修改
        // 下面四句是验证channel 的正确性
        int fd = channel->fd();
        (void)fd;
        assert(channels_.find(fd) != channels_.end());
        assert(channels_[fd] == channel);
        assert(index == kAdded);

        // 既然已经添加了,那么可能的修改就是修改监听的时间,或者不在监听
        // 因此这里先判断是否是没有监听的事件了,如果是那么直接移除、
        if (channel->isNoneEvent())
        {
            update(EPOLL_CTL_DEL, channel);
            channel->set_index(kDeleted);
        }
        else
        {
            // 否则,就是要更新事件。
            update(EPOLL_CTL_MOD, channel);
        }
    }
}

// 移除channel
void EPollPoller::removeChannel(Channel *channel)
{
    // 验证 channel 的正确性
    Poller::assertInLoopThread();
    int fd = channel->fd();
    LOG_TRACE << "fd = " << fd;
    assert(channels_.find(fd) != channels_.end());
    assert(channels_[fd] == channel);
    assert(channel->isNoneEvent());

    // 继续验证,是判断 index ,也就是channel 要移除一定不是 new,
    // 因为new 还没有添加到  channels_ 中
    int index = channel->index();
    assert(index == kAdded || index == kDeleted);
    size_t n = channels_.erase(fd);
    (void)n;
    assert(n == 1);

    // 开始正式的判断
    // 如果是 add ,那么就从epoll 中删除
    if (index == kAdded)
    {
        update(EPOLL_CTL_DEL, channel);
    }
     // 然后都是设置为new ,表示没有添加在  channels_ 中
    channel->set_index(kNew);
    // 一般这个channel 是要被析构了
}

// 更新
void EPollPoller::update(int operation, Channel *channel)
{
    // 每次都要传入,因此,每次都是新的结构体.
    struct epoll_event event;
    bzero(&event, sizeof event);
    // 读取channel中的要设置的events
    event.events = channel->events();

    // 设置 data_ptr 指向封装的结构体
    event.data.ptr = channel;
    int fd = channel->fd();

    // 打印log
    LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
              << " fd = " << fd << " event = { " << channel->eventsToString() << " }";
    // 实际的 epoll_ctl
    if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
    {
        if (operation == EPOLL_CTL_DEL)
        {
            LOG_SYSERR << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
        }
        else
        {
            LOG_SYSFATAL << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
        }
    }
}

// 打印状态
const char *EPollPoller::operationToString(int op)
{
    switch (op)
    {
    case EPOLL_CTL_ADD:
        return "ADD";
    case EPOLL_CTL_DEL:
        return "DEL";
    case EPOLL_CTL_MOD:
        return "MOD";
    default:
        assert(false && "ERROR op");
        return "Unknown Operation";
    }
}

3 channels_ 存在的意思

epoll 没有办法保存已有的所有的在监听的文件描述符,因为epoll采用增量的形式传入epoll_event的,因此之前传入的就传入了,没有办法去获取出来。

因此使用 channels_  来保存所有的 channel 也就是说所有的文件描述符。

用来在新添加和修改删除文件描述符的时候做验证。

原文地址:https://www.cnblogs.com/perfy576/p/8656832.html

时间: 2024-10-12 08:04:26

muduo源码-EpollPoller.h的相关文章

muduo源码-EventLoop.h

0 设计 EventLoop 类,主要是用来管理一个进程中的channel.计时器.以及epoll 的类. 每个类只有一个,(因为如果有两个,那么一个eventloop 在loop()的时候,另一个eventloop 得不到执行). 每一个channel或者说是每一个文件描述符都必须属于eventloop,换句话说,每一个文件描述符属于一个线程.(这是必然的,因为eventloop在代表了一个线程) eventloop 类的主要工作,就是wait epoll,然后执行channel 的事件处理函

muduo源码-HttpRequest.h

1 设计 HttpRequest 类,是解析完毕的请求体所存放的地方,被关联在 HttpConnext中. 2 源码 #ifndef MUDUO_NET_HTTP_HTTPREQUEST_H #define MUDUO_NET_HTTP_HTTPREQUEST_H #include <muduo/base/copyable.h> #include <muduo/base/Timestamp.h> #include <muduo/base/Types.h> #includ

muduo源码-HttpResponse.h

1 设计 HttpResponse类用来,存放需要发送给客户端的数据. 这些数据是一些原始的数据.并没有合成完整的报文. 但是提供一个接口,可以填充传入的 buffer 对象,合成完整的响应报文. 2 源码 #ifndef MUDUO_NET_HTTP_HTTPRESPONSE_H #define MUDUO_NET_HTTP_HTTPRESPONSE_H #include <muduo/base/copyable.h> #include <muduo/base/Types.h>

muduo源码分析--我对muduo的理解

分为几个模块 EventLoop.TcpServer.Acceptor.TcpConnection.Channel等 对于EventLoop来说: 他只关注里面的主驱动力,EventLoop中只关注poll,这类系统调用使得其成为Reactor模式,EventLoop中有属于这个loop的所有Channel,这个loop属于哪一个Server. 几个类存在的意义: 从应用层使用的角度来看,用户需要初始化一个EventLoop,然后初始化一个TcpServer(当然也可以自定义个TcpServer

muduo源码分析--Reactor模式在muduo中的使用

一. Reactor模式简介 Reactor释义"反应堆",是一种事件驱动机制.和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的时间发生,Reactor将主动调用应用程序注册的接口,这些接口又称为"回调函数". 二. moduo库Reactor模式的实现 muduo主要通过3个类来实现Reactor模式:EventLoop,Chann

muduo源码分析--Reactor模式的在muduo中的使用(二)

一. TcpServer类: 管理所有的TCP客户连接,TcpServer供用户直接使用,生命期由用户直接控制.用户只需设置好相应的回调函数(如消息处理messageCallback)然后TcpServer::start()即可. 主要数据成员: boost::scoped_ptr<Accepter> acceptor_; 用来接受连接 std::map<string,TcpConnectionPtr> connections_; 用来存储所有连接 connectonCallbac

SGI STL源码stl_bvector.h分析

前言 上篇文章讲了 STL vector 泛化版本的实现,其采用普通指针作为迭代器,可以接受任何类型的元素.但如果用来存储 bool 类型的数据,可以实现功能,但每一个 bool 占一个字节(byte),而一个字节有 8 位(bit),这样就有点浪费了.所以 SGI STL 设计了一个特化版本的位向量容器 bit_vector 来节省空间内存.bit_vector 是一个 bit 位元素的序列容器,具有 vector 容器一样的成员函数,常用于硬件端口的控制. 原文地址:https://www.

(素材源码)猫猫学IOS(二十六)UI之iOS抽屉效果小Demo

猫猫分享,必须精品 素材代码地址:http://download.csdn.net/detail/u013357243/8635679 原创文章,欢迎转载.转载请注明:翟乃玉的博客 地址:http://blog.csdn.net/u013357243?viewmode=contents 先看效果 源码 NYDrawViewController.h // // NYDrawViewController.h // 06-抽屉效果 // // Created by apple on 14-9-1. /

h5微信房卡牛牛源码学习讲解

h5微信房卡牛牛源码Q 2171793408 官网地址: http://wowotouba.com/h5 比较仔细的学习了<c++primer>,并对每个习题都自己写代码实现了一遍,包括稍微复杂一点的例子. 认真读完了<effective c++>,<effective stl>. 比较仔细的学完了<数据结构与算法分析>,并把其中的每种数据结构和算法都用c++实现了一遍.包括各种线性表,树(二叉树.AVL树.RB树的各种操作),图(BFS.DFS.prim.