轉自:http://name5566.com/4198.html
?
參考文獻列表:
http://www.wangafu.net/~nickm/libevent-book/
此文編寫的時候,使用到的 Libevent 為 2.0.21。本文略過了關于 event 優先權和超時相關的討論。
創建和銷毀 event_base
event_base 是首先需要被創建出來的對象。event_base 結構持有了一個 event 集合。如果 event_base 被設置了使用鎖,那么它在多個線程中可以安全的訪問。但是對 event_base 的循環(下面會馬上解釋什么是“對 event_base 的循環”)只能在某個線程中執行。如果你希望多個線程進行循環,那么應該做的就是為每一個線程創建一個 event_base。
event_base 存在多個后端可以選擇(我們也把 event_base 后端叫做 event_base 的方法):
- select
- poll
- epoll
- kqueue
- devpoll
- evport
- win32
在我們創建 event_base 的時候,Libevent 會為我們選擇最快的后端。創建 event_base 通過函數 event_base_new() 來完成:
- // 創建成功返回一個擁有默認設置的 event base
- // 創建失敗返回 NULL
- struct event_base * event_base_new ( void );
- ?
- // 查看 event_base 實際上使用到的后端
- const char * event_base_get_method ( const struct event_base * base );
對于大多數程序來說,默認設置已經夠用了。
event_base 的釋放使用函數:
- void event_base_free ( struct event_base * base );
事件循環(event loop)
event_base 會持有一組 event(這是我們前面說到的),換而言之就是說,我們可以向 event_base 中注冊 event(具體如何注冊本文先不談)。如果我們向 event_base 中注冊了一些 event,那么就可以讓 Libevent 開始事件循環了:
- // 指定一個 event_base 并開始事件循環
- // 此函數內部被實現為一個不斷進行的循環
- // 此函數返回 0 表示成功退出
- // 此函數返回 -1 表示存在未處理的錯誤
- int event_base_dispatch ( struct event_base * base );
默認的事件循環會在以下的情況停止(也就是 event_base_dispatch 會返回):
- 如果 base 中沒有 event,那么事件循環將停止
- 調用 event_base_loopbreak(),那么事件循環將停止
- 調用 event_base_loopexit(),那么事件循環將停止
- 如果出現錯誤,那么事件循環將停止
事件循環會檢測是否存在活躍事件(之前已經介紹過活躍事件這一術語: http://name5566.com/4190.html ),若存在活躍事件,那么調用事件對應的回調函數。
停止事件循環的可以通過移除 event_base 中的 event 來實現。如果你希望在 event_base 中存在 event 的情況下停止事件循環,可以通過以下函數完成:
- // 這兩個函數成功返回 0 失敗返回 -1
- // 指定在 tv 時間后停止事件循環
- // 如果 tv == NULL 那么將無延時的停止事件循環
- int event_base_loopexit ( struct event_base * base ,
- const struct timeval * tv );
- // 立即停止事件循環(而不是無延時的停止)
- int event_base_loopbreak ( struct event_base * base );
這里需要區別一下 event_base_loopexit(base, NULL) 和 event_base_loopbreak(base):
- event_base_loopexit(base, NULL) 如果當前正在為多個活躍事件調用回調函數,那么不會立即退出,而是等到所有的活躍事件的回調函數都執行完成后才退出事件循環
- event_base_loopbreak(base) 如果當前正在為多個活躍事件調用回調函數,那么當前正在調用的回調函數會被執行,然后馬上退出事件循環,而并不處理其他的活躍事件了
有時候,我們需要在事件的回調函數中獲取當前的時間,這時候你不需要調用 gettimeofday() 而是使用 event_base_gettimeofday_cached()(你的系統可能實現 gettimeofday() 為一個系統調用,你可以避免系統調用的開銷):
- // 獲取到的時間為開始執行此輪事件回調函數的時間
- // 成功返回 0 失敗返回負數
- int event_base_gettimeofday_cached ( struct event_base * base ,
- struct timeval * tv_out );
如果我們需要(為了調試)獲取被注冊到 event_base 的所有的 event 和它們的狀態,調用 event_base_dump_events() 函數:
- // f 表示輸出內容的目標文件
- void event_base_dump_events ( struct event_base * base , FILE * f );
event
現在開始詳細的討論一下 event。在 Libevent 中 event 表示了一組條件,例如:
- 一個文件描述符可讀或者可寫
- 超時
- 出現一個信號
- 用戶觸發了一個事件
event 的相關術語:
- 一個 event 通過 event_new() 創建出來,那么它是已初始化的,另外 event_assign() 也被用來初始化 event(但是有它特定的用法)
- 一個 event 被注冊到(通過 add 函數添加到)一個 event_base 中,其為 pending 狀態
- 如果 pending event 表示的條件被觸發了,那么此 event 會變為活躍的,其為 active 狀態,則 event 相關的回調函數被調用
- event 可以是 persistent(持久的)也可以是非 persistent 的。event 相關的回調函數被調用之后,只有 persistent event 會繼續轉為 pending 狀態。對于非 persistent 的 event 你可以在 event 相關的事件回調函數中調用 event_add() 使得此 event 轉為 pending 狀態
event 常用 API:
- // 定義了各種條件
- // 超時
- #define EV_TIMEOUT 0x01
- // event 相關的文件描述符可以讀了
- #define EV_READ 0x02
- // event 相關的文件描述符可以寫了
- #define EV_WRITE 0x04
- // 被用于信號檢測(詳見下文)
- #define EV_SIGNAL 0x08
- // 用于指定 event 為 persistent
- #define EV_PERSIST 0x10
- // 用于指定 event 會被邊緣觸發(Edge-triggered 可參考 http://name5566.com/3818.html )
- #define EV_ET 0x20
- ?
- // event 的回調函數
- // 參數 evutil_socket_t --- event 關聯的文件描述符
- // 參數 short --- 當前發生的條件(也就是上面定義的條件)
- // 參數 void* --- 其為 event_new 函數中的 arg 參數
- typedef void (* event_callback_fn )( evutil_socket_t , short , void *);
- ?
- // 創建 event
- // base --- 使用此 event 的 event_base
- // what --- 指定 event 關心的各種條件(也就是上面定義的條件)
- // fd --- 文件描述符
- // cb --- event 相關的回調函數
- // arg --- 用戶自定義數據
- // 函數執行失敗返回 NULL
- struct event * event_new ( struct event_base * base , evutil_socket_t fd ,
- short what , event_callback_fn cb ,
- void * arg );
- ?
- // 釋放 event(真正釋放內存,對應 event_new 使用)
- // 可以用來釋放由 event_new 分配的 event
- // 若 event 處于 pending 或者 active 狀態釋放也不會存在問題
- void event_free ( struct event * event );
- ?
- // 清理 event(并不是真正釋放內存)
- // 可用于已經初始化的、pending、active 的 event
- // 此函數會將 event 轉換為非 pending、非 active 狀態的
- // 函數返回 0 表示成功 -1 表示失敗
- int event_del ( struct event * event );
- ?
- // 用于向 event_base 中注冊 event
- // tv 用于指定超時時間,為 NULL 表示無超時時間
- // 函數返回 0 表示成功 -1 表示失敗
- int event_add ( struct event * ev , const struct timeval * tv );
一個范例:
- #include <event2/event.h>
- ?
- // event 的回調函數
- void cb_func ( evutil_socket_t fd , short what , void * arg )
- {
- const char * data = arg ;
- printf ( "Got an event on socket %d:%s%s%s%s [%s]" ,
- ( int ) fd ,
- ( what & EV_TIMEOUT ) ? " timeout" : "" ,
- ( what & EV_READ ) ? " read" : "" ,
- ( what & EV_WRITE ) ? " write" : "" ,
- ( what & EV_SIGNAL ) ? " signal" : "" ,
- data );
- }
- ?
- void main_loop ( evutil_socket_t fd1 , evutil_socket_t fd2 )
- {
- struct event * ev1 , * ev2 ;
- struct timeval five_seconds = { 5 , 0 };
- // 創建 event_base
- struct event_base * base = event_base_new ();
- ?
- // 這里假定 fd1、fd2 已經被設置好了(它們都被設置為非阻塞的)
- ?
- // 創建 event
- ev1 = event_new ( base , fd1 , EV_TIMEOUT | EV_READ | EV_PERSIST , cb_func ,
- ( char *) "Reading event" );
- ev2 = event_new ( base , fd2 , EV_WRITE | EV_PERSIST , cb_func ,
- ( char *) "Writing event" );
- ?
- // 注冊 event 到 event_base
- event_add ( ev1 , & five_seconds );
- event_add ( ev2 , NULL );
- // 開始事件循環
- event_base_dispatch ( base );
- }
Libevent 能夠處理信號。信號 event 相關的函數:
- // base --- event_base
- // signum --- 信號,例如 SIGHUP
- // callback --- 信號出現時調用的回調函數
- // arg --- 用戶自定義數據
- #define evsignal_new ( base , signum , callback , arg ) \
- event_new ( base , signum , EV_SIGNAL | EV_PERSIST , cb , arg )
- ?
- // 將信號 event 注冊到 event_base
- #define evsignal_add ( ev , tv ) \
- event_add (( ev ),( tv ))
- ?
- // 清理信號 event
- #define evsignal_del ( ev ) \
- event_del ( ev )
在通常的 POSIX 信號處理函數中,不少函數是不能被調用的(例如,不可重入的函數),但是在 Libevent 中卻沒有這些限制,因為信號 event 設定的回調函數運行在事件循環中。另外需要注意的是,在同一個進程中 Libevent 只能允許一個 event_base 監聽信號。
性能相關問題
Libevent 提供了我們機制來重用 event 用以避免 event 在堆上的頻繁分配和釋放。相關的接口:
- // 此函數用于初始化 event(包括可以初始化棧上和靜態存儲區中的 event)
- // event_assign() 和 event_new() 除了 event 參數之外,使用了一樣的參數
- // event 參數用于指定一個未初始化的且需要初始化的 event
- // 函數成功返回 0 失敗返回 -1
- int event_assign ( struct event * event , struct event_base * base ,
- evutil_socket_t fd , short what ,
- void (* callback )( evutil_socket_t , short , void *), void * arg );
- ?
- // 類似上面的函數,此函數被信號 event 使用
- #define evsignal_assign ( event , base , signum , callback , arg ) \
- event_assign ( event , base , signum , EV_SIGNAL | EV_PERSIST , callback , arg )
已經初始化或者處于 pending 的 event,首先需要調用 event_del() 后再調用 event_assign()。
這里我們進一步認識一下 event_new()、event_assign()、event_free()、event_del()
event_new() 實際上完成了兩件事情:
- 通過內存分配函數在堆上分配了 event
- 使用 event_assign() 初始化了此 event
event_free() 實際上完成了兩件事情:
- 調用 event_del() 進行 event 的清理工作
- 通過內存分配函數在堆上釋放此 event
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
