【Poller/EPollPoller】

在muduo网络库的设计中Poller是个抽象虚类,由EpollPoller和PollPoller继承实现,EpollPoller就是封装了用epoll方法实现的与事件监听有关的各种方法。

与 Channel 的协作:Poller 维护了一个 `ChannelMap`,用于存储需要监听的 Channel 对象。Poller 的 poll 方法会遍历这个 `ChannelMap`,并调用底层的系统调用(如 poll 或 epoll_wait)来监听 I/O 事件。当有事件发生时,Poller 会更新 Channel 的 `revents` 成员变量,并将发生事件的 Channel 加入到 `activeChannels` 列表中返回给 EventLoop。

成员变量

protected:
    ChannelMap channels_; // 需要事件监听的Channel集合

private:
    EventLoop* ownerLoop_; // 当前Poller 关联的 EventLoop
    
public:
    //Poller关注的Channel
    using ChannelList = std::vector<Channel*>;
    // 保存fd -> Channel的映射map集合
    using ChannelMap = std::map<int, Channel*>;

主要来讲讲EPollPoller

成员变量

private:
 // epoll 监视的文件描述符
    int epollfd_;
// 用来存储活跃文件描述符的 epoll_event 结构体数组
    EventList events_; // epoll_wait 收集事件的集合

public:
	using EventList = std::vector<struct epoll_event>;

常量定义

// 定义 Channel 在 Poller 中的状态
    static const int kNew = -1;    // Channel 要添加到 map 和 epoll 红黑树中
    static const int kAdded = 1;   // Channel 在 map 和 epoll 的红黑树中
    static const int kDeleted = 2; // Channel 在 map 中,但是不在 epoll 的红黑树中

// 默认监听事件数量
    static const int kInitEventListSize = 16;

重要方法

通过epoll_wait将发生事件的channel通过`activeChannels`告知给EventLoop

在这个函数中,实际上就是调用了`epoll_wait`得到了事件发生的集合,然后调用`fillActiveChannels` 将发生的事件装入`activeChannels`

`activeChannels` 是 `ChannelList = std::vector<Channel*>`;类型,将监听到该fd发生的事件写进这个Channel中的`revents`成员变量中。这样获取到了发生事件的集合,然后把这个Channel装进`activeChannels`中,当外界调用完poll之后就能拿到事件监听器的监听结果,在EventLoop中就可以对它进行处理

Timestamp poll(int timeoutMs, ChannelList *activeChannels)
        {
            // 调用 epoll_wait 等待事件发生
            int numEvents = ::epoll_wait(epollfd_, events_.data(), events_.size(), timeoutMs);
            int savedErrno = errno;
            Timestamp now(Timestamp::Now());

            if (numEvents > 0)   //有事件到达
            {

                // 填充活跃的 Channel 到 activeChannels 中
                fillActiveChannels(numEvents, activeChannels);
                // 如果事件数量达到 events_ 的大小,将 events_ 扩容
                if (static_cast<size_t>(numEvents) == events_.size())
                {
                    events_.resize(events_.size() * 2);
                }
            }
            else if (numEvents == 0)   //没有事件到达
            {
                LOG_TRACE << " nothing happended";
            }
            else
            {
                // 处理错误情况
                if (savedErrno != EINTR)
                {
                    errno = savedErrno;
                    LOG_SYSERR << "EPollPoller::poll()";
                }
            }
            return now;
        }

 值得注意的是,我们平常使用epoll_wait传入的是数组 ,而在此传的是vector,通过.data(),得到vector的frst指针,指向vector容器首元素的地址;比数组优势的一点在于扩容。

void fillActiveChannels(int numEvents, ChannelList *activeChannels) const
        {
            assert(static_cast<size_t>(numEvents) <= events_.size());
            for (int i = 0; i < numEvents; ++i)
            {
                Channel *channel = static_cast<Channel *>(events_[i].data.ptr);
                // 确保 Channel 在 channels_ 中
                assert(channels_.find(channel->fd()) != channels_.end());
                assert(channels_.find(channel->fd())->second == channel);
                // 设置 Channel 发生的事件
                channel->set_revents(events_[i].events);
                // 将 Channel 添加到 activeChannels 中
                activeChannels->push_back(channel);
            }
        }

 对于events_[i].data.ptr进行进一步解释:

struct epoll_event {
    uint32_t     events;      // 触发的事件类型(如 EPOLLIN、EPOLLOUT)
    epoll_data_t data;       // 用户数据
};

typedef union epoll_data {
    void        *ptr;        // 指向用户数据的指针
    int          fd;         // 文件描述符
    uint32_t     u32;        // 32位整数
    uint64_t     u64;        // 64位整数
} epoll_data_t;

`data` 是一个联合体(union),只能存储其中一种类型的值。Muduo 选择使用 `ptr` 字段,将其指向对应的 `Channel` 对象。

这么设计的好处也显而易见 `epoll` 返回的事件与对应的 `Channel` 对象高效绑定,实现 O (1) 时间复杂度的事件分发

那接着又有一个疑问了, Channel *和void *之间的转换安全吗?

答案是肯定的,陈硕这个大佬,这么设计的思路也是很nb了。因为:

  • 存入和取出时使用相同的类型转换。

取出就是我看见的fillActiveChannels函数里用到的,

存入则在updata函数里使用

 // 更新 channel 通道,本质是调用了 epoll_ctl
        void update(int operation, Channel *channel)
        {
            struct epoll_event event;
            bzero(&event, sizeof event);
            event.events = channel->events(); //注册fd感兴趣的事件
            event.data.ptr = channel;
            int fd = channel->fd();
            // 调用 epoll_ctl 进行添加、修改或删除操作
            if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
            {
                if (operation == EPOLL_CTL_DEL)
                {
                    LOG_SYSERR << "epoll_ctl op=" << operation << " fd= " << fd;
                }
                else
                {
                    LOG_SYSFATAL << "epoll_ctl op=" << operation << " fd= " << fd;
                }
            }
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值