1. 程式人生 > >muduo_net程式碼剖析之EventLoop

muduo_net程式碼剖析之EventLoop

TCP網路程式設計本質

TCP網路程式設計最本質的是處理三個半事件

  1. 連線建立:伺服器accept(被動)接收連線,客戶端connect(主動)發起連線
  2. 連線斷開:主動斷開(close、shutdown),被動斷開(read返回0)
  3. 訊息到達:檔案描述符可讀
  4. 訊息傳送完畢:這算半個。對於低流量的服務,可不必關心這個事件;這裡的傳送完畢是指資料寫入作業系統緩衝區,將由TCP協議棧負責資料的傳送與重傳,不代表對方已經接收到資料

在這裡插入圖片描述

EventLoop是整個Reactor的核心。其類圖如下:
在這裡插入圖片描述
one loop per thread意味著每個執行緒只能有一個EventLoop物件,用變數

__thread EventLoop* t_loopInThisThread = 0;

表示,在建立EventLoop物件時將t_loopInThisThread賦值,以後再建立時就可以檢查這個變數,如果已經賦值就說明當前執行緒已經建立過EventLoop物件了。執行緒呼叫靜態函式EventLoop::getEventLoopOfCurrentThread就可以獲得當前執行緒的EventLoop物件的指標了。

對EventLoop的個人理解

  1. 與libevent的event_base類似,EventLoop也是一個事件迴圈,即EventLoop本質上是一個while迴圈,在while迴圈中呼叫了epoll_wait/poll監聽註冊在EventLoop上的事件/Channel
  2. EventLoop的使用與libevent的event_base一樣,就是呼叫loop()函式對安插在EventLoop物件上的事件進行監聽

EventLoop::loop()的時序圖

在這裡插入圖片描述

  1. loop.loop()實際上是在while迴圈中呼叫了poll或epoll的poll函式,程式在此處阻塞,直到有事件被觸發
  2. 當監聽到事件被觸發後,呼叫handleEvent()函式去處理事件
//事件迴圈,該函式不能跨執行緒呼叫,只能在建立該物件的執行緒中呼叫
void EventLoop::loop()
{
  assert(!looping_); 
  assertInLoopThread
(); //斷言當前處於建立該物件的執行緒中 looping_ = true; quit_ = false; // FIXME: what if someone calls quit() before loop() ? LOG_TRACE << "EventLoop " << this << " start looping"; while (!quit_) { //清除活動通道 activeChannels_.clear(); //呼叫poll()[ 本質上epoll的epoll_wait、poll的poll() ],阻塞在這裡 //poll返回後,activeChannels_活動通道被填充 pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); ++iteration_; if (Logger::logLevel() <= Logger::TRACE) { printActiveChannels(); } eventHandling_ = true; //遍歷每一個活動通道,並處理事件handleEvent for (Channel* channel : activeChannels_) { currentActiveChannel_ = channel; currentActiveChannel_->handleEvent(pollReturnTime_); } currentActiveChannel_ = NULL; eventHandling_ = false; doPendingFunctors(); //執行pendingFunctors_中的任務 } LOG_TRACE << "EventLoop " << this << " stop looping"; looping_ = false; }

示例程式碼

父子執行緒都建立了自己的loop,並且監控安插在各自身上的事件

#include <muduo/net/EventLoop.h>
#include <muduo/base/Thread.h>

#include <assert.h>
#include <stdio.h>
#include <unistd.h>

using namespace muduo;
using namespace muduo::net;

void callback()
{
  printf("callback(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid());
  EventLoop anotherLoop;
}

void threadFunc()
{
  printf("threadFunc(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid());

  assert(EventLoop::getEventLoopOfCurrentThread() == NULL);
  EventLoop loop; //子執行緒中的loop
  assert(EventLoop::getEventLoopOfCurrentThread() == &loop);
  loop.runAfter(1.0, callback); //1s後,呼叫callback
  loop.loop();
}

int main()
{
  printf("main(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid());

  assert(EventLoop::getEventLoopOfCurrentThread() == NULL);
  EventLoop loop; //主執行緒中的loop
  assert(EventLoop::getEventLoopOfCurrentThread() == &loop);

  Thread thread(threadFunc);
  thread.start(); //啟動子執行緒

  loop.loop(); //主執行緒中的loop呼叫loop()
}