天天看點

libev網絡庫的架構解析

作者:linux技術棧

libev庫的設計架構簡介

年初就已經将libev庫的源碼通讀過,初次感覺非常晦澀難懂。但是随着反複的閱讀,感覺其設計思想及效率優化方面都非常出色。

libev庫不是一個單線程的架構麼?怎麼應用于大型伺服器網絡模型中呢?後續通過memcached、muduo等庫的閱讀。才深入了解了該庫的架構模型。libev庫不涉及具體的網絡程式設計,而真正的主流網絡程式設計架構需要基于此基礎上實作,還需要大量的工作。

在高性能的網絡程式中,使用得最廣泛的要數“non-blocking IO+IO multiplexing”這種模型(Reactor模式)。

在該模型中,程式基本結構是: 一個事件循環(eventloop),以事件驅動(event-driven)和事件回調方式實作業務的邏輯。

while(!done)
{
int timeout_ms = max(1000, getNextTimedCa llback();
 int retVal = ::poll(fds, nfs, timeout_ms);
 if(retVal < 0){
 //處理錯誤 
 } else {
 // 處理到期的timers,回調使用者的timer handler
 if(retVal > 0){
  // 處理IO事件,回調使用者的IO event handler
  }
 }
}           

libev網絡庫的核心架構可以簡單的用上述代碼表示,這也可以看作Reactor模式的架構。

網絡程式設計中有很多是事務性的工作,可以提取為公用的架構或庫,而使用者隻需要填上關鍵的業務邏輯代碼,并将回調注冊到架構中,就可以實作完整的網絡服務,這正是Reactor模式的主要思想。

Reactor模式介紹

在Reactor中,有5個關鍵的參與者:

描述符(handle):由OS提供,用于識别每一個時間。

同步事件分離器(demultiplexer):是一個函數,用來等待一個或多個事件的發生。調用者會被阻塞,知道分離器分離的描述符集上有事件發生。 Linux上select、poll、epoll等都是經常被使用的。

事件處理器接口(event handler):由一個或多個模闆函數組成的接口,這些模闆函數描述了應用程式相關的對某個時間的操作。

具體的事件處理器(concrete event handler):事件處理器接口的實作。它實作了應用程式提供的某個服務。每個具體的事件處理器總和一個描述符相關。

Reactor管理器(reactor):定義一些接口,用于應用程式控制事件排程、時間處理器的注冊、删除等。它是時間處理器的排程核心。一旦事件發生,Reactor管理器先是分離每個事件,然後排程時間處理器(調用相關模闆函數來處理事件)。

libev網絡庫的架構解析

該模式圖可以大緻對應上述代碼段:

同步事件分離器: 即對應代碼中的poll(2) 函數【當然,還包括select(2)/epoll(4),在libev中對應的是接口函數backend_poll】;

事件處理器:即上述代碼中timer handler/ IO event handler段,用于處理不同的注冊事件【libev中事件注冊後續将介紹】;

Reactor管理器:即上述代碼中整個while循環,用于事件注冊、排程、删除等【對應libev中的ev_run( )函數】。

Reactor模式作為網絡伺服器的常用模型,優點明顯,能夠提高并發量和吞吐量,對于IO密集的應用是不錯的選擇。但是基于事件驅動的模型也有其缺陷:它要求事件回調函數必須是非阻塞的;且對于設計網絡IO的請求響應式協定,它容易割裂業務邏輯,使其分散于多個回調函數之中。

相關視訊推薦

90 分鐘搞懂 libevent 如何解決網絡問題

全網最詳細epoll講解,6種epoll的設計,讓你吊打面試官

C++後端必讀7個開源項目源碼(redis、mysql、protobuf、libevent..)

需要C/C++ Linux伺服器架構師學習資料加qun812855908擷取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

libev網絡庫的架構解析

libev的事件定義

libev的事件結構

libev中支援多種事件,包括IO事件、定時器事件(絕對定時、相對定時)、信号等等。對于每一種事件,都有結構體與之對應。如ev_io、ev_timer、ev_signal、ev_child等。

libev在C中使用結構體布局實作了多态,可以将ev_watcher結構體看做所有ev_TYPE結構體的基類。

/* 該宏為所有ev_TYPE的"基類",為其他事件結構所擁有 */
#define EV_WATCHER(type) 
int active; /* private */ \ /*該watcher是否被激活,加入到loop中*/
int pending; /* private */ \ /*該watcher關注的events是否已觸發*/
EV_DECL_PRIORITY /* private */ \ /*int priority; 優先級,watcher是有優先級的*/
EV_COMMON /* rw */ \ /*void *data*/
EV_CB_DECLARE (type) /* private */ /*void (*cb)(struct ev_loop *loop, type *w, int revents);回調函數*/
/*構成事件連結清單*/
#define EV_WATCHER_LIST(type)	\
EV_WATCHER (type)	\
struct ev_watcher_list *next; /*該成員變量用于将watcher串起來*/ 
 
/*在EV_WATCHER基礎上增加時間戳,定義與定時器相關的事件*/
#define EV_WATCHER_TIME(type)	\
EV_WATCHER (type)	\
ev_tstamp at; /* private */           

通過以上的結構,libev定義了公用的結構體(可以了解為"基類")。從上述結構中可以看出,每個事件結構的公有字段包括:事件狀态、優先級、參數資訊、回調函數等。

//内容就是EV_WATCHER宏的内容
typedef struct ev_watcher
{
EV_WATCHER (ev_watcher)
} ev_watcher;
//内容就是EV_WATCHER_TIME宏的内容
typedef struct ev_watcher_time
{
EV_WATCHER_TIME (ev_watcher_time)
} ev_watcher_time;
//可以了解為一個帶有next指針的基類
typedef struct ev_watcher_list
{
EV_WATCHER_LIST (ev_watcher_list)
} ev_watcher_list;           

對于具體的事件(可以了解為ev_watcher的"派生類"),主要列舉常用的IO、定時器、信号事件:

//ev_io 封裝io事件的"派生類",結構體前部就是宏EV_WATCHER_LIST,fd和events是"派生類"變量
typedef struct ev_io
{
EV_WATCHER_LIST (ev_io)
 
int fd; /* ro */
int events; /* ro */
} ev_io;
 
//ev_signal 封裝信号事件的"派生類",signum是"派生類"變量
typedef struct ev_signal
{
EV_WATCHER_LIST (ev_signal)
 
int signum; /* ro */
} ev_signal;
 
//ev_timer 封裝相對定時器事件的"派生類",定時器用堆管理,不需要next指針
typedef struct ev_timer
{
EV_WATCHER_TIME (ev_timer)
 
ev_tstamp repeat; /* rw */
} ev_timer;            

每個不同僚件都有該類型事件的私有資訊,比如IO事件對應的fd、定時器時間的發生時間等。

事件的初始化

事件的初始化也可以看做"基類"的初始化、以及"派生類"的初始化。 "基類"的初始化定義宏 ev_init(ev,cb_)

#define ev_init(ev,cb_) do {	\
((ev_watcher *)(void *)(ev))->active =	\
((ev_watcher *)(void *)(ev))->pending = 0;	\
ev_set_priority ((ev), 0);	\
ev_set_cb ((ev), cb_);	\
} while (0)           

"派生類"的初始化,例舉IO事件、timer事件,如下:

//IO事件的初始化
#define ev_io_init(ev,cb,fd,events) \ 
do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)
 
#define ev_io_set(ev,fd_,events_) \
do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)
 
//timer事件的初始化
#define ev_timer_init(ev,cb,after,repeat) \
do { ev_init ((ev), (cb)); ev_timer_set ((ev),(after),(repeat)); } while (0)
 
#define ev_timer_set(ev,after_,repeat_) \
do { ((ev_watcher_time *)(ev))->at = (after_); (ev)->repeat = (repeat_); } while (0)           

從上面各個事件的初始化宏定義可以看出,都是先調用公共的初始化 ev_init(2),然後調用自身的初始化函數ev_TYPE_set()。

libev事件的啟動、停止

通過以上對libev事件結構的定義,事件的初始化的分析。即完成了事件的初始設定,其他事件也是類同。下面分别解析IO事件、定時器時間是如何啟動的。

事件啟動、停止的函數的格式為:ev_TYPE_start( ), ev_TYPE_stop( )。

同樣有"基類"的啟動、停止。分别為ev_start() ev_stop()。

void ev_start (EV_P_ W w, int active) 
{
pri_adjust (EV_A_ w); //調整優先級
w->active = active;	//watcher激活
ev_ref (EV_A);	//loop的激活引用遞增
}
 
void ev_stop (EV_P_ W w) 
{
ev_unref (EV_A); //loop的引用遞減
w->active = 0;
}           

等待執行清單 pendings

pendings為事件等待執行清單(pendings為指針數組,當啟動優先級模式,則優先級即為指針數組的數組下标。否則為普通模式),隻有當事件觸發了,才将事件添加到該清單中。數組結構為:

typedef struct
{
W w; //events隻指這個watcher關注了并且已經發生了的,還沒有處理的事件 
int events; 
} ANPENDING;           
libev網絡庫的架構解析

最終每次的loop循環,在ev_invoke_pending()函數中,會依次調用各個watcher的回調函數,優先級從高到低(如果開啟存在優先級模式)。

IO事件的相關存儲結構

IO事件存儲需要兩種結構: ANFD *anfd 和 int *fdchanges 。其中:

typedef struct
{
//typedef ev_watcher_list *WL; 關注同一個fd的事件的watcher連結清單,一個fd可以有多個watcher監聽它的事件
WL head;
//watcher連結清單中所有watcher關注事件的按位與
unsigned char events;
//當這個結構體需要重新epoll_ctl則設定,說明關注的事件發生了變化
unsigned char reify; 
//epoll後端使用,實際發生的事件
unsigned char emask; 
unsigned char unused;
} ANFD;           

anfd 存放未注冊的事件,且以fd為下标,後面挂接該fd不同的事件(讀事件、寫事件,分别有各自的回調函數)。

fdchanges 對于啟動的fd(ev_io_start( )函數中調用),将fd加入該數組中。該數組存在的目的是:為了每次loop循環時,将fdchanges裡面在ev_io_start裡面設定記錄的這些新事件一個個處理,真正加入epoll裡面( 即fd_reify()函數的實作, fd的具現化)。

libev網絡庫的架構解析

關于IO事件,重點有兩個函數: fd_change(), fd_reify()。

fd_change(): 該函數在ev_io_start(), ev_stop()中調用,通過anfds的reify标志判斷是否需要加入 fdchanges數組中。

fd_reify(): 該函數在ev_run()的每輪循環中都會調用。将fdchanges中記錄的這些新事件一個個的處理,并調用後端IO複用的backend_modify宏。

這裡需要注意fd_reify()中的思想,anfd[fd] 結構體中,還有一個events事件,它是原先的所有watcher 的事件的 "|" 操作,向系統的epoll 從新添加描述符的操作 是在下次事件疊代開始前進行的,當我們依次掃描fdchangs,找到對應的anfd 結構,如果發現先前的events 與 目前所有的watcher 的"|" 操作結果不等,則表示我們需要調用epoll_ctrl 之類的函數來進行更改,反之不做操作。

IO事件的啟動、停止

IO事件啟動的主要代碼

<pre name="code" class="cpp">void  ev_io_start (EV_P_ ev_io *w) 
{
  //激活相關 
  ev_start (EV_A_ (W)w, 1);  
 
  //判斷目前的anfds能否存放fd, 若不能則重新配置設定空間
  array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);
 
   //将該事件插入到anfds[fd]的頭部,挂一個未處理事件,比如讀事件、寫事件
  wlist_add (&anfds[fd].head, (WL)w);     
  //将該fd加入到fdchanges數組裡,
  fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);
 
  //start結束,取消該事件的标記(init時候添加)
  w->events &= ~EV__IOFDSET;
}           

IO事件停止的主要代碼

void ev_io_stop (EV_P_ ev_io *w) 
{
//如果該事件正在pending(等待執行的事件)中,從pending清單中移除該事件。
//這裡的一個技巧是不用真的移除掉(數組删除複雜度O(n)),隻要将pending清單對應位置的指針指向一個空事件就可以了。
clear_pending (EV_A_ (W)w);
//從連結清單中删除一個節點
wlist_del (&anfds[w->fd].head, (WL)w);
//取消fd的active狀态
ev_stop (EV_A_ (W)w);
//将fd加到fdchanges數組中,隻設定REIFY标記,表示有改動
//之後事件驅動器掃描fdchanges數組會發現該fd不再監聽任何事件,作出相應操作
fd_change (EV_A_ w->fd, EV_ANFD_REIFY);
}           

Timer事件的相關存儲結構

Timer事件存儲結構為ANHE *timers 以及W* rfeeds。其中:

typedef struct {
ev_tstamp at;
WT w;
} ANHE;
 
#define ANHE_w(he) (he).w /* access watcher, read-write */
#define ANHE_at(he) (he).at /* access cached at, read-only */
#define ANHE_at_cache(he) (he).at = (he).w->at /* update at from watcher */           

對每一個注冊的Timer事件,其存放在timers[]數組中,并以heap結構方式存儲在數組中(下面隻對2-heap進行分析)。 堆的基本操作包括向上調整堆upheap、向下調整堆downheap。

upheap: 将該節點與其父結點進行比較,如果其值比父結點小,則交換,然後對其父結點重複upheap操作。

downheap:與其兩個子節點比較(也可能一個),如果兩個子節點中有一個是小于目前節點的值,則交換并重複以上操作,如果兩個子節點都小于目前節點的值,則選擇最小的那個交換并重複,如果2個子節點都大于等于目前的權,當然不用做任何操作了。

當我們添加一個timer時,我們直接在數組末尾位置添加,然後執行upheap 操作,其複雜度也是O(lgn);當我們删除一個節點,則用最後一個timer替換待删除的timer,然後對新交換的timer進行堆調整。

關于IO事件,重點函數為:timers_reify()。

timers_reify(): 用目前時間與根節點時間比較,逾時則加入到待處理隊列中,然後進行堆調整,再次與根節點比較。主體代碼如下:

void timers_reify (EV_P) 
{
if (timercnt && ANHE_at (timers [HEAP0]) < mn_now) 
{
do
{
ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]);
//如果定時器重複,則w的目前計時+repeat,組成下一個 
if (w->repeat)
{
ev_at (w) += w->repeat;
if (ev_at (w) < mn_now)
ev_at (w) = mn_now;
ANHE_at_cache (timers [HEAP0]);
//向下調整堆,定時器仍然存在該數組中
downheap (timers, timercnt, HEAP0);
}
else
//該定時器watcher關閉
ev_timer_stop (EV_A_ w); /* nonrepeating: stop timer */
//将逾時的事件加入rfeeds[]結構中 
feed_reverse (EV_A_ (W)w); 
}
while (timercnt && ANHE_at (timers [HEAP0]) < mn_now);
//将逾時的事件加入rfeeds[]結構中
feed_reverse_done (EV_A_ EV_TIMER);
}
}           
libev網絡庫的架構解析
libev網絡庫的架構解析

Timer事件的啟動、停止

Timer事件啟動的主體代碼

void ev_timer_start (EV_P_ ev_timer *w) 
{
//更新目前時間 
ev_at (w) += mn_now; 
//定時器累加 
++timercnt; 
//事件激活相關 其中active即為timercnt(timers下标) 
ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1); 
array_needsize (ANHE, timers, timermax, ev_active (w) + 1, EMPTY2);
ANHE_w (timers [ev_active (w)]) = (WT)w;
ANHE_at_cache (timers [ev_active (w)]);
//新添加的元素放在數組後面,然後向上調整堆 
upheap (timers, ev_active (w));
}           

Timer事件停止的主體代碼

void ev_timer_stop (EV_P_ ev_timer *w) 
{
//清除pendings[]激活事件隊列中,關于w的事件
clear_pending (EV_A_ (W)w);
 
//清除pendings[]激活事件隊列中,關于w的事件
int active = ev_active (w);
 
//定時器個數減1(即timer[]數組個數減1)
--timercnt;
 
if (expect_true (active < timercnt + HEAP0)) 
{
//timers [active]中的元素用數組最後一個元素替換
timers [active] = timers [timercnt + HEAP0]; 
//比較下标為active處的元素與其父節點的元素,來決定采用向上、向下調整
adjustheap (timers, timercnt, active); 
}
ev_at (w) -= mn_now; 
ev_stop (EV_A_ (W)w);
}           

libev的事件排程

libev的ev_loop結構

struct ev_loop結構是該庫的主要結構,很多變量都是基于該結構的(ev_loop的具體結構參見 Ev.c)。而EV_MULTIPLICITY是一個條件編譯的宏,表明是否支援有多個ev_loop執行個體存在。一般而言,每個線程中有且僅有一個ev_loop執行個體。如果整個上層業務都是單線程的,程式中可以不适用EV_MULTIPLICITY宏,則很多變量都是全局變量(具體變量的相關資訊參考"ev_vars.h"頭檔案)。

#if EV_MULTIPLICITY
struct ev_loop;
# define EV_P struct ev_loop *loop /* a loop as sole parameter in a declaration */
# define EV_P_ EV_P, /* a loop as first of multiple parameters */
# define EV_A loop /* a loop as sole argument to a function call */
# define EV_A_ EV_A, /* a loop as first of multiple arguments */
#else
# define EV_P void
# define EV_P_
# define EV_A
# define EV_A_
#endif           

從上面宏定義可知,如果未定義EV_MULTIPLICITY, void ev_io_start (EV_P_ ev_io *w) 會被編譯成ev_io_start (ev_io *w) ,否則編譯成 ev_io_start(struct ev_loop *loop, ev_io *w)。

libev的事件排程

libev的整個事件都是在ev_run()函數中完成的。該函數相當于Reactor管理器角色,負責事件注冊、排程、删除等。ev_run( ) 中代碼較多,這裡簡要給出整個loop的流程圖:

libev網絡庫的架構解析

libev的背景複用

libev使用函數指針來實作不同的IO複用機制,每個複用機制都要實作init,modify,poll,destroy這些函數,也就是初始化、修改關注事件、等待事件發生、銷毀等。具體IO複用機制的選取在loop_init()函數中完成。