(四)循环分发事件

前面的基础库已经足够我们目前使用,开始小试网络库吧!

1. EventLoop

创建EventLoop 的线程将是IO 线程,而One Loop Per Thread 认为每个线程只有一个EventLoop,不可跨线程调用(跨线程调用没有意义,若出现证明设计出错);

assertInLoopThread() 实现了此设计的检错;

  void assertInLoopThread() {
        if (!isInLoopThread()) {
            abortNotInLoopThread();
        }
    }

EventLoop 持有:

class EventLoop {
	// ...
	typedef std::vector<Channel *> ChannelList;
	private:
    std::unique_ptr<Poller> _poller;
    ChannelList _activeChannels;
	// ...
}

开启事件循环监听:

    while (!_quit) {
        _activeChannels.clear();
        _poller->poll(kPollTimeMs, &_activeChannels);
        for (auto iter : _activeChannels) {
            iter->handleEvent();
        }
    }

首先清空上轮活跃通道的记录,poll 轮询事件发生,并将通道存储于_activeChannels,再依次进行调用;

2. Poller

Poller 有指向拥有该Poller 的EventLoop指针,从而保证当前线程为IO 线程;
Poller 管理fd 表和相应的通道信息;

class Poller {
	// ...
	private:
	// ...
    EventLoop *_ownerLoop;
    PollFdList _pollfds;
    ChannelMap _channels;

}

紧接上Poller::poll 的详细介绍:

Timestamp Poller::poll(int timeoutMs, ChannelList *activeChannels) {
    int numEvents = ::poll(_pollfds.data(), _pollfds.size(), timeoutMs);
    Timestamp now(Timestamp::now());
    if (numEvents > 0) {
        LOG_TRACE << numEvents << " events happened";
        fillActiveChannels(numEvents, activeChannels);
    } else if (numEvents == 0) {
        LOG_TRACE << " nothing happended";
    } else {
        LOG_SYSERR << "Poller::poll()";
    }

    return now;
}

poll 查看_pollfds 中有反应的文件描述符,numEvents 接受其数量,并通过fillActiveChannels 添加到活跃通道中:

void Poller::fillActiveChannels(int numEvents,
                                ChannelList *activeChannels) const {

    for (auto pfd = _pollfds.begin(); pfd != _pollfds.end() && numEvents > 0;
         pfd++) {
        if (pfd->revents > 0) {
            --numEvents;
            auto ch = _channels.find(pfd->fd);
            assert(ch != _channels.end());
            auto channel = ch->second;
            assert(channel->fd() == pfd->fd);
            channel->set_revents(pfd->revents);
            activeChannels->push_back(channel);
        }
    }
}

pfd->revents 包含此文件符的含义,POLLIN POLLOUT POLLPRI … 若为真证明是poll 中反应文件描述符;

接着在_channels (map<fd, channel>)找到相应Channel(Channel 是提前设置的,在loop 前),并设置revents,将此通道放入活跃通道中等待回调;

3. Channel

开启事件分发要:

在这里插入图片描述

  1. 生成EventLoop
  2. 创建文件描述符
  3. 设置通道(一个通道对应一个fd,并有对应的回调函数和事件信息,可以通过控制通道实现对fd 的管理,如禁止读写等)
  4. 开启Loop

设置通道包括安置回调函数和开启读,前者就是记录回调函数,后者复杂;

enableReadiing 会使当前Channel 管理的fd 改变Poll 属性,从而使得在handleEvent(Loop 中会以iter->handleEvent() 调用)以不同方式进行;而enableReading 后,作为Loop 的主体,EventLoop 自然持有Channel信息(_activeChannel),但是轮询的Poller 没有(只有fd <-> Channel 对应表),所以要从Channel 跳转到Loop 再更新Poller 中的数据,分两种情况:

  1. 没有此描述符
if (channel->index() < 0) {
        assert(_channels.find(channel->fd()) == _channels.end());
        struct pollfd pfd;
        pfd.fd = channel->fd();
        pfd.events = static_cast<short>(channel->events());
        pfd.revents = 0;
        _pollfds.push_back(pfd);
        int idx = static_cast<int>(_pollfds.size()) - 1;
        channel->set_index(idx);
        _channels[pfd.fd] = channel;
    } 
  1. 有此描述符
else {
        assert(_channels.find(channel->fd()) != _channels.end());
        assert(_channels[channel->fd()] == channel);
        int idx = channel->index();
        assert(idx >= 0 && idx < static_cast<int>(_pollfds.size()));
        struct pollfd &pfd = _pollfds[idx];
        assert(pfd.fd == channel->fd() || pfd.fd == -1);
        pfd.events = static_cast<short>(channel->events());
        pfd.revents = 0;
        if (channel->isNoneEvent()) {
            pfd.fd = -1;
        }
    }

第二种情况若当前通道被设置为黑名单,则忽视:后面将pfd.fd = -1 改为求相反数再减一(暂且留坑);

4. Other

关于前向声明:两个头文件相互引用时,在头文件剔除包含另一个头文件,并使用前向声明,然后在源文件包含另一个头文件;

Muduo Log 中getenv 可以获取当前日志的系统变量来确定Log 等级,可以在export MUDUO_LOG_TRACE=1 中实现类似效果;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt的事件循环是一个非常重要的概念,也是Qt能够实现异步、响应式编程的关键所在。简单来说,事件循环是一个无限循环,用于处理Qt应用程序中的各种事件,包括用户输入、定时器事件、网络事件等等。以下是Qt事件循环的原理: 1. 事件队列:Qt应用程序中的所有事件都被放置在一个事件队列中,事件队列是一个先进先出的队列结构。 2. 事件循环:Qt应用程序启动后,会进入一个无限循环,也就是事件循环事件循环会不断地从事件队列中取出事件,并将事件分发给对应的对象进行处理。 3. 事件分发:当事件循环事件队列中取出一个事件时,会根据事件的类型和目标对象,将事件分发给对应的对象进行处理。如果事件是一个用户输入事件,那么它会被分发给当前获得焦点的控件进行处理;如果事件是一个定时器事件,那么它会被分发给对应的定时器对象进行处理。 4. 事件处理:一旦事件分发给对应的对象,该对象就会调用自己的事件处理函数来处理事件。例如,一个按钮控件的事件处理函数会检测用户是否点击了该按钮,如果是,则执行与该按钮相关的操作。 5. 事件过滤器:Qt框架允许对象在事件处理之前拦截事件进行处理,这个过程称为事件过滤。事件过滤器可以用来处理一些全局事件,例如窗口关闭事件、应用程序退出事件等等。 总之,Qt的事件循环是一个非常灵活、高效的机制,可以保证Qt应用程序能够实现异步、响应式编程。开发者只需要关注对象的事件处理函数,无需关心事件循环的具体实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值