muduo網路庫學習筆記(9):Reactor模式的關鍵結構

li27z發表於2016-09-19

Reactor模式簡介

Reactor的意思是“反應堆”,是一種事件驅動機制。它和普通函式呼叫的不同之處在於:應用程式不是主動的呼叫某個API完成處理,而是恰恰相反,Reactor逆置了事件處理流程,應用程式需要提供相應的介面並註冊到Reactor上,如果相應的事件發生,Reactor將主動呼叫應用程式註冊的介面,這些介面又稱為“回撥函式”。

moduo庫Reactor模式的實現

muduo中Reactor的關鍵結構包括:EventLoop、Poller和Channel。

類圖如下:
這裡寫圖片描述

如類圖所示,EventLoop類和Poller類屬於組合的關係,EventLoop類和Channel類屬於聚合的關係…

我們這裡補充一下這兩種類與類之間的關係:
I.聚合關係
聚合是關聯關係的一種特例,它體現的是整體與部分的關係,即has-a的關係。此時整體與部分之間是可分離的,它們可以具有各自的生命週期,部分可以屬於多個整體物件,也可以為多個整體物件共享。比如計算機與CPU、公司與員工的關係等。在UML類圖設計中,聚合關係以空心菱形表示。
例:這裡寫圖片描述

II.組合關係
組合也是關聯關係的一種特例,它體現的是一種contains-a的關係,這種關係比聚合更強,也稱為強聚合。它同樣體現整體與部分間的關係,但此時整體與部分是不可分的,整體的生命週期結束也就意味著部分的生命週期結束,比如人和人的大腦。在UML類圖設計中,組合關係以實心菱形表示。
例: 這裡寫圖片描述


我們下面根據事件的迴圈流程來理解muduo對Reactor模式的實現。
時序圖:
這裡寫圖片描述

EventLoop::loop()呼叫Poller::poll()獲得當前活動事件的Channel列表,再遍歷該列表,執行每個Channel的Channel::handleEvent()完成相應就緒事件回撥。

程式碼片段1:EventLoop::loop()
檔名:EventLoop.cc

// 事件迴圈,該函式不能跨執行緒呼叫
// 只能在建立該物件的執行緒中呼叫
void EventLoop::loop()
{
  assert(!looping_);
  // 斷言當前處於建立該物件的執行緒中
  assertInLoopThread();
  looping_ = true;
  LOG_TRACE << "EventLoop " << this << " start looping";

  while (!quit_)
  {
    activeChannels_.clear();  // 清空當前vector中的所有元素
    // 呼叫Poller::poll()返回活動的通道
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
    if (Logger::logLevel() <= Logger::TRACE)
    {
      printActiveChannels();
    }
    eventHandling_ = true;
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)
    {
      currentActiveChannel_ = *it;
      // 呼叫Channel::handleEvent()完成相應的事件回撥
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
  }

Poller class是IO多路複用的封裝,在muduo中它是一個抽象基類,因為muduo同時支援poll和epoll兩種IO多路複用機制。

程式碼片段2:PollPoller::poll()
檔名:PollPoller.cc

// PollPoller::poll()是PollPoller的核心功能
// 它呼叫poll()獲得當前活動的IO事件
// 然後填充呼叫方傳入的activeChannels
// 並返回poll return的時刻
Timestamp PollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
  /**
   * poll函式原型: 
   * int poll(struct pollfd *fds, unsigned long nfds, int timeout);
   * 返回值:若有就緒描述符則為其數目,若超時則為0,若出錯則為-1
   * 
   * 這裡直接把vector<struct pollfd>pollfds_傳給poll
   * &*pollfds_.begin()是獲得元素的首地址,表示式型別符合函式要求
   */
  int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs);
  Timestamp now(Timestamp::now());
  if (numEvents > 0)
  {
    LOG_TRACE << numEvents << " events happended";

    // fillActiveChannels()會遍歷pollfds_
    // 找出有活動事件的fd
    // 把它對應的Channel填入activeChannels
    fillActiveChannels(numEvents, activeChannels);
  }
  else if (numEvents == 0)
  {
    LOG_TRACE << " nothing happended";
  }
  else
  {
    LOG_SYSERR << "PollPoller::poll()";
  }
  return now;
}
程式碼片段3:PollPoller::fillActiveChannels()
檔名:PollPoller.cc

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)
    {
      // 每找到一個活動fd就遞減numEvents
      --numEvents;
      // ChannelMap是從fd到Channel*的對映
      ChannelMap::const_iterator ch = channels_.find(pfd->fd);
      assert(ch != channels_.end());
      Channel* channel = ch->second;
      assert(channel->fd() == pfd->fd);
      // 將當前活動事件revents儲存到Channel中
      // 供Channel::handleEvent()使用
      channel->set_revents(pfd->revents);
      activeChannels->push_back(channel);
    }
  }
}

Channel::handleEvent()呼叫Channel::handleEventWithGuard()處理事件。

程式碼片段4:Channel::handleEventWithGuard()
檔名:Channel.cc

// 根據revents_的值分別呼叫不同的使用者回撥
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
  eventHandling_ = true;
  // POLLHUP:發生掛起
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
  {
    if (logHup_)
    {
      LOG_WARN << "Channel::handle_event() POLLHUP";
    }
    if (closeCallback_) closeCallback_();
  }

  // POLLNVAL:描述符不是一個開啟的檔案
  if (revents_ & POLLNVAL)
  {
    LOG_WARN << "Channel::handle_event() POLLNVAL";
  }

  // POLLERR:發生錯誤
  if (revents_ & (POLLERR | POLLNVAL))
  {
    if (errorCallback_) errorCallback_();
  }
  // POLLIN:普通或優先順序帶資料可讀
  // POLLPRI:高優先順序資料可讀
  // POLLRDHUP:優先順序帶資料可讀
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
  {
    if (readCallback_) readCallback_(receiveTime);
  }
  // POLLOUT:普通資料可寫
  if (revents_ & POLLOUT)
  {
    if (writeCallback_) writeCallback_();
  }
  eventHandling_ = false;
}

下面我們再來看一下註冊和更新IO事件的流程
時序圖:
這裡寫圖片描述
enableReading()函式會加入可讀事件,然後執行Channel的Update()函式,Channel::Update()會呼叫EventLoop::updateChannel(),後者會轉而呼叫Poller::updateChannel()。

程式碼片段5:PollPoller::updateChannel()
檔名:PollPoller.cc

void PollPoller::updateChannel(Channel* channel)
{
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
  // index()返回在poll的事件陣列中的序號,index_在建構函式中的初始值為-1
  // index < 0說明是一個新的通道
  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;
  }
  // 是已有的通道
  else
  {
    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;
    // 將通道暫時更改為不關注事件,但不從Poller中移除該通道
    if (channel->isNoneEvent())
    {
      // 如果某個Channel暫時不關心任何事件
      // 就把pfd.fd直接設定為-1,讓poll忽略此項
      // 這裡的設定是為了removeChannel優化
      pfd.fd = -channel->fd()-1; 
    }
  }
}
程式碼片段6:PollPoller::removeChannel()
檔名:PollPoller.cc

void PollPoller::removeChannel(Channel* channel)
{
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd();
  assert(channels_.find(channel->fd()) != channels_.end());
  assert(channels_[channel->fd()] == channel);
  // removeChannel前需要先updateChannel為不關注事件
  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)
  {
    pollfds_.pop_back();
  }
  else
  {
    // 將待刪除元素與最後一個元素交換再pop_back,演算法時間複雜度是O(1)
    int channelAtEnd = pollfds_.back().fd;
    iter_swap(pollfds_.begin()+idx, pollfds_.end()-1);
    if (channelAtEnd < 0)
    {
      channelAtEnd = -channelAtEnd-1;
    }
    channels_[channelAtEnd]->set_index(idx);
    pollfds_.pop_back();
  }
}

相關文章