muduo網路庫學習筆記(9):Reactor模式的關鍵結構
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();
}
}
相關文章
- muduo網路庫學習筆記(1):Timestamp類筆記
- muduo網路庫學習筆記(2):原子性操作筆記
- muduo網路庫學習筆記(3):Thread類筆記thread
- muduo網路庫學習筆記(11):有用的runInLoop()函式筆記OOP函式
- muduo網路庫學習筆記(14):chargen服務示例筆記
- muduo網路庫學習筆記(13):TcpConnection生命期的管理筆記TCP
- muduo網路庫學習筆記(10):定時器的實現筆記定時器
- muduo網路庫學習筆記(8):高效日誌類的封裝筆記封裝
- muduo網路庫學習筆記(5):執行緒池的實現筆記執行緒
- muduo網路庫學習筆記(12):TcpServer和TcpConnection類筆記TCPServer
- muduo網路庫學習筆記(7):執行緒特定資料筆記執行緒
- muduo網路庫學習筆記(15):關於使用stdio和iostream的討論筆記iOS
- muduo網路庫學習筆記(6):單例類(執行緒安全的)筆記單例執行緒
- muduo網路庫學習筆記(4):互斥量和條件變數筆記變數
- muduo網路庫學習之muduo_http 庫涉及到的類HTTP
- muduo網路庫學習之muduo_inspect 庫涉及到的類
- 層模式——面向模式體系結構學習筆記模式筆記
- muduo網路庫學習之EventLoop(七):TcpClient、ConnectorOOPTCPclient
- CMake構建學習筆記9-Eigen庫的構建筆記
- 表示-抽象-控制——系統結構模式學習筆記抽象模式筆記
- Swift學習筆記(3)iOS 9 中的網路請求Swift筆記iOS
- Javascript中的關鍵字'this'學習筆記JavaScript筆記
- Go 結構 學習筆記Go筆記
- Object C學習筆記24-關鍵字總結Object筆記
- 吳恩達機器學習筆記 —— 9 神經網路學習吳恩達機器學習筆記神經網路
- muduo網路庫學習之EventLoop(五):TcpConnection生存期管理(連線關閉)OOPTCP
- 關於網路安全的逆向分析方向學習筆記筆記
- 【學習筆記】網路流筆記
- [網路]NIO學習筆記筆記
- 網路流學習筆記筆記
- this 關鍵字的理解--java學習筆記(轉)Java筆記
- muduo網路庫學習之EventLoop(四):EventLoopThread 類、EventLoopThreadPool 類OOPthread
- C#設計模式學習筆記:(9)組合模式C#設計模式筆記
- GO 學習筆記->結構體Go筆記結構體
- 資料結構學習筆記資料結構筆記
- muduo網路庫Timestamp類
- muduo網路庫使用心得
- 《laravel 框架關鍵技術解析》學習筆記之裝飾者模式Laravel框架筆記模式