Mudu分析及总结(三)Poller、PollPoller

    xiaoxiao2025-04-04  39

    Poller是基类,PollPoller和EPollPoller是子类,本文只介绍Poller和PollPoller。

    Poller操作Channel对象,但是不拥有Channel对象。PollPoller是基于poll实现,EPollPoller是基于epoll实现。

    一、类关系图

    二、工作时序图

    三、poll方法基本概念

    1、poll()

    int poll (struct pollfd *fds, size_t nfds , int timeout);

    参数解释:

    fds:指向struct pollfd数组nfds:struct pollfd数组元素的个数,表示要检测几个pollfd。timeout:表示poll函数的超时时间,单位是毫秒。

    函数返回值:

    大于0:表示监听的文件描述符中就绪可操作的文件描述符个数。等于0:表示无活跃的文件描述符,超时返回。小于0:表示出错。

    2、struct pollfd

    struct pollfd { int fd; /* 文件描述符 */ short events; /* 告诉操作系统要监听的事件(可以多个事件) */ short revents; /* 操作系统返回的实际发生事件 */ };

    三、Poller

    Poller是基类,没什么实现。

    1、Poller.h

    class Poller : boost::noncopyable { public: typedef std::vector<Channel*> ChannelList; //活跃的通道 Poller(EventLoop* loop); virtual ~Poller(); // /// Polls the I/O events. /// Must be called in the loop thread. virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0; /// Changes the interested I/O events. /// Must be called in the loop thread. virtual void updateChannel(Channel* channel) = 0; /// Remove the channel, when it destructs. /// Must be called in the loop thread. virtual void removeChannel(Channel* channel) = 0; static Poller* newDefaultPoller(EventLoop* loop); void assertInLoopThread() { ownerLoop_->assertInLoopThread(); } private: EventLoop* ownerLoop_; //Poller的拥有者EventLoop };

    四、PollPoller.h

    class PollPoller : public Poller { public: PollPoller(EventLoop* loop); virtual ~PollPoller(); //查询出活跃的通道 virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels); //更新通道 virtual void updateChannel(Channel* channel); //移除通道,一般在调用此方法之前,都会调用Channel->disableAll() virtual void removeChannel(Channel* channel); private: void fillActiveChannels(int numEvents, ChannelList* activeChannels) const; typedef std::vector<struct pollfd> PollFdList; typedef std::map<int, Channel*> ChannelMap; PollFdList pollfds_; //fd的集合 ChannelMap channels_; //fd与Channel的映射 };

    五、PollPoller.cc

    PollPoller::PollPoller(EventLoop* loop) : Poller(loop) { } PollPoller::~PollPoller() { } Timestamp PollPoller::poll(int timeoutMs, ChannelList* activeChannels) { //通过poll查询是否有活跃的文件描述符 // XXX pollfds_ shouldn't change int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs); Timestamp now(Timestamp::now()); if (numEvents > 0) { //返回值大于0表示对应个数活跃的文件描述符 //调用函数,获取活跃的文件描述符。 LOG_TRACE << numEvents << " events happended"; fillActiveChannels(numEvents, activeChannels); } else if (numEvents == 0) { //无任何活跃的文件描述符 LOG_TRACE << " nothing happended"; } else { //出错 LOG_SYSERR << "PollPoller::poll()"; } return now; } void PollPoller::fillActiveChannels(int numEvents,ChannelList* activeChannels) const { for (PollFdList::const_iterator pfd = pollfds_.begin(); pfd != pollfds_.end() && numEvents > 0; ++pfd) { //循环取值 if (pfd->revents > 0) { //有事件发生时,操作系统会给struct pollfd的revents赋值,值大于0 --numEvents; ChannelMap::const_iterator ch = channels_.find(pfd->fd); assert(ch != channels_.end()); Channel* channel = ch->second; assert(channel->fd() == pfd->fd); channel->set_revents(pfd->revents); // pfd->revents = 0; activeChannels->push_back(channel); } } } void PollPoller::updateChannel(Channel* channel) { Poller::assertInLoopThread(); LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events(); if (channel->index() < 0) { //index小于0表示是一个新的Channel, //添加到管理队列 // a new one, add to pollfds_ 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; } else { //index大于0表示队列中存在此通道,所以进行更新。 // update existing one assert(channels_.find(channel->fd()) != channels_.end()); //校验 assert(channels_[channel->fd()] == channel);//校验 int idx = channel->index(); assert(0 <= idx && idx < static_cast<int>(pollfds_.size())); struct pollfd& pfd = pollfds_[idx]; assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1); pfd.events = static_cast<short>(channel->events());//用户请求事件保存 pfd.revents = 0; if (channel->isNoneEvent()) { //如果通过被设置为无任何事件,则将fd设置为负数, //至于为什么至于设置,下文会解释 // ignore this pollfd pfd.fd = -channel->fd()-1; } } } void PollPoller::removeChannel(Channel* channel) { //移除通道 Poller::assertInLoopThread(); LOG_TRACE << "fd = " << channel->fd(); assert(channels_.find(channel->fd()) != channels_.end()); assert(channels_[channel->fd()] == channel); assert(channel->isNoneEvent()); int idx = channel->index(); assert(0 <= idx && idx < static_cast<int>(pollfds_.size())); const struct pollfd& pfd = pollfds_[idx]; (void)pfd; assert(pfd.fd == -channel->fd()-1 && pfd.events == channel->events()); size_t n = channels_.erase(channel->fd()); assert(n == 1); (void)n; if (implicit_cast<size_t>(idx) == pollfds_.size()-1) { //如果index正好是列表的结尾,则直接pop释放。 pollfds_.pop_back(); } else { //如果删除的struct pollfd不是列表的结尾, //出于效率考虑,直接将要删除的pollfd与尾部的 //pollfd交换,然后释放 int channelAtEnd = pollfds_.back().fd; iter_swap(pollfds_.begin()+idx, pollfds_.end()-1); if (channelAtEnd < 0) { //因为在updateChannel时,是将通道进行取反-1, //所以这里原样取回。 channelAtEnd = -channelAtEnd-1; } channels_[channelAtEnd]->set_index(idx); pollfds_.pop_back(); } }

    六、关键点

    1、pfd.fd = -channel->fd()-1;

    如果某一个channel暂时不关心任何事件,那么可以吧pollfd.fd设置为负数,这样poll会忽略此文件描述符。不能将pollfd.events设置为0,因为无法屏蔽POLLERR事件。pfd.fd再次进行减一是为了解决fd可能为0的情况,-0还是0,所以减1。updateChannel()更新时若不关心任何事件,一开始是把fd设置为-1的,但是为了配合removeChannel()进行高效的删除struct pollfd,就直接去fd的负数了。因为删除时还需要用到fd。

    2、fd置位负数,未置位正数问题

    当进行updateChannel时,若通道不再关系任何事件,则将struct pollfd对应的fd设置为负数,一开始我还在找这里设置为了负数。当通道再次关注事件时应该设置为正数,将fd还原回来,但Muduo实现中却没有,只有在进行removeChannel的时候对fd进行取正。 通过查看源码调用

    void TcpConnection::connectDestroyed() { loop_->assertInLoopThread(); assert(state_ == kConnected || state_ == kDisconnecting); setState(kDisconnected); channel_->disableAll(); connectionCallback_(shared_from_this()); loop_->removeChannel(get_pointer(channel_)); }

    channel->disableAll()与removeChannel()是成对出现的,channel->disableAll()将通道设置为不关心事件通道,也就是将fd设置为负数,然后removeChannel()中就移除掉此通道,所以说我上面疑问的将fd设置为负数,而没其他地方再将fd设置为正数。因为已经没这个必要了,fd设置为负数,接下来就是移除此通道。

    3、删除fd小技巧

    删除channel时,由于pollfds_使用的是vector来存储channel的,可以根据channle本身的index_来确定该channel在vector中所在的位置。如果直接删除该位置的话,那么后面的channel都需要上移一个位置,导致后面的channel都需要更新自己的index_,这样效率会很低。这里有一个小技巧,就是直接将vector最后的元素A与该位置进行呼唤,只需要修改A的index_就好,其他的channel不受影响

    4、fillActiveChannels设计

    之所以需要返回activateChannels,而不是在遍历发生事件的文件描述符的同时执行对应的channel中的handleEvent函数,是因为handleEvent函数有可能会对pollfds_进行修改,如删除channel,使得在迭代期间pollfds_大小发生改变,这是一件很危险的事情。另一个原因就是简化Poller的职责,Poller只负责监听,不负责事件的分发与处理。


    备注: 参考链接【http://liubigbin.github.io/2016/07/20/muduo中的Channel和Poller/】

    最新回复(0)