事件庫之Libev(一)
使用Libev
Libev的作者寫了一份很好的官方Manual,比較的齊全,即介紹了Libev的設計思想,也介紹了基本使用還包括內部各類事件詳細介紹。這裡略微贅述一下。Libev通過一個 ·struct ev_loop· 結結構表示一個事件驅動的框架。在這個框架裡面通過ev_xxx
結構,ev_init
、ev_xxx_set
、ev_xxx_start
介面箱這個事件驅動的框架裡面註冊事件監控器,當相應的事件監控器的事件出現時,便會觸發該事件監控器的處理邏輯,去處理該事件。處理完之後,這些監控器進入到下一輪的監控中。符合一個標準的事件驅動狀態的模型。
Libev 除了提供了基本的三大類事件(IO事件、定時器事件、訊號事件)外還提供了週期事件、子程序事件、檔案狀態改變事件等多個事件,這裡我們用三大基本事件寫一個例子,和
-
#include<ev.h>
-
#include <stdio.h>
-
#include <signal.h>
-
#include <sys/unistd.h>
-
ev_io io_w;
-
ev_timer timer_w;
-
ev_signal signal_w;
-
void io_action(struct ev_loop *main_loop,ev_io *io_w,int e)
-
{
-
int rst;
-
char buf[1024] = {'\0'};
-
puts("in io cb\n");
-
read(STDIN_FILENO,buf,sizeof(buf));
-
buf[1023] = '\0';
-
printf("Read in a string %s \n",buf);
-
ev_io_stop(main_loop,io_w);
-
}
-
void timer_action(struct ev_loop *main_loop,ev_timer *timer_w,int e)
-
{
-
puts("in tiemr cb \n");
-
ev_timer_stop(main_loop,timer_w);
-
}
-
void signal_action(struct ev_loop *main_loop,ev_signal signal_w,int e)
-
{
-
puts("in signal cb \n");
-
ev_signal_stop(main_loop,signal_w);
-
ev_break(main_loop,EVBREAK_ALL);
-
}
-
int main(int argc ,char *argv[])
-
{
-
struct ev_loop *main_loop = ev_default_loop(0);
-
ev_init(&io_w,io_action);
-
ev_io_set(&io_w,STDIN_FILENO,EV_READ);
-
ev_init(&timer_w,timer_action);
-
ev_timer_set(&timer_w,2,0);
-
ev_init(&signal_w,signal_action);
-
ev_signal_set(&signal_w,SIGINT);
-
ev_io_start(main_loop,&io_w);
-
ev_timer_start(main_loop,&timer_w);
-
ev_signal_start(main_loop,&signal_w);
-
ev_run(main_loop,0);
-
return 0;
-
}
下面對使用到的這些API進行說明。
這裡使用了3種事件監控器,分別監控IO事件、定時器事件以及訊號事件。因此定義了3個監控器(watcher),以及觸發監控器時要執行動作的回撥函式。Libev定義了多種監控器,命名方式為 ev_xxx
這裡xxx代表監控器型別,其實現是一個結構體,
-
typedef struct ev_io
-
{
-
....
-
} ev_io;
通過巨集定義可以簡寫為 ev_xxx
。回撥函式的型別為 void cb_name(struct ev_loop *main_loop,ev_xxx *io_w,int event)
。
在main中,首先定義了一個事件驅動器的結構 struct ev_loop *main_loop
這裡呼叫 ev_default_loop(0)
生成一個預製的全域性驅動器。這裡可以參考Manual中的選擇。然後依次初始化各個監控器以及設定監控器的觸發條件。
初始化監控器的過程是將相應的回撥函式即觸發時的動作註冊到監控器上。
設定觸發條件則是該條件產生時才去執行註冊到監控器上的動作。對於IO事件,一般是設定特定fd上的的可讀或可寫事件,定時器則是多久後觸發。這裡定時器的觸發條件中還有第三引數,表示第一次觸發後,是否迴圈,若為0則吧迴圈,否則按該值迴圈。訊號觸發器則是設定觸發的訊號。
在初始化並設定好觸發條件後,先呼叫ev_xxx_start
將監控器註冊到事件驅動器上。接著呼叫 ev_run
開始事件驅動器。
在事件的觸發動作裡面。我加入了一個 ev_xxx_stop
函式,與上面對應,也就是講改監控器從事件驅動器裡面登出掉。使其不再起作用。而在訊號觸發的動作中還加入了一個 ev_break
該函式可以使程序跳出 main_loop
事件驅動器迴圈,也就是關閉事件驅動器。結束這一邏輯。
libev最簡單的示例就是這樣的一個結構。定義一個監控器、書寫觸發動作邏輯、初始化監控器、設定監控器觸發條件、將監控器加入大事件驅動器的迴圈中即可。一個比較清晰的事件驅動框架。
libev的事件驅動過程可以想象成如下的虛擬碼:
-
do_some_init()
-
is_run = True
-
while is_run:
-
t = caculate_loop_time()
-
deal_loop(t)
-
deal_with_pending_event()
-
do_some_clear()
首先做一些初始化操作,然後進入到迴圈中,該迴圈通過一個狀態位來控制是否執行。在迴圈中,計算出下一次輪詢的時間,這裡輪詢的實現就採用了系統提供的epoll、kqueue等機制。再輪詢結束後檢查有哪些監控器的被觸發了,依次執行觸發動作。這裡不要糾結訊號事件、定時器時間咋都經過了 deal_loop
libev是如何實現的這裡暫且不討論,這個虛擬碼只是大致表示下libev的整體框架。