【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent
轉載地址:https://blog.csdn.net/jiange_zh/article/details/50631393
簡介
由于本項目是純異步的,而對于大量 socket 連接,使用 select 并不高效。(參見我的另一篇博文:epoll簡介)
事實上,大部分系統提供了處理大量 socket 連接的解決方案:
由于各個平臺使用了不同的接口,所以想編寫跨平臺的高性能異步程序就需要做一層跨平臺封裝。此時 Libevent 是一個不錯的選擇(當然你也可以自己來實現這個事-。-),其最底層 API(event 和 event_base API)為各個平臺實現高性能異步程序提供了一致的接口。
一些概念
event:會綁定文件描述符、回調函數并表示一個或者多個條件(例如,文件描述符可以讀、寫或者超時等)。event 表示的條件如果被觸發了,那么 event 會變為活躍的,它綁定的回調函數就會被執行。
event_base: 持有一組 event 并進行事件循環,event_base 會存在一個后端(也叫做方法),常見的后端包括 epoll、kqueue 等。
結構
組件:
evutil 用于抽象不同的平臺的網絡(基礎的)實現;
event、event_base 為 Libevent 的核心,為不同的平臺下基于事件的非阻塞 I/O 提供了一套抽象的接口;
bufferevent 對 Libevent 的基于事件的核心的封裝。應用程序的讀寫請求是基于緩沖區的;
evbuffer 為 bufferevent 實現的緩沖區;
evhttp 一個簡單的 HTTP client/server 的實現;
evdns 一個簡單的 DNS client/server 的實現;
evrpc 一個簡單的 RPC 實現;
庫:
libevent_core 包括 util、event_base、evbuffer、bufferevent;
libevent_extra 包括 HTTP、DNS、RPC;
libevent 此庫由于歷史原因而存在,不要使用它;
libevent_pthreads 此庫為基于 pthread 的線程和鎖的實現;
libevent_openssl 此庫通過 openssl 和 bufferevent 提供了加密通訊;
頭文件:?
? ? 所有的公用頭文件位于 event2 目錄中。
創建和銷毀 event_base
event_base 是最基本的,故需要首先被創建。
event_base 結構持有一個 event 集合。如果 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
// 創建失敗返回 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,那么就可以讓 libevent 開始事件循環了:
// 指定一個 event_base 并開始事件循環
// 此函數內部被實現為一個不斷進行的循環
// 此函數返回 0 表示成功退出
// 此函數返回 -1 表示存在未處理的錯誤
int event_base_dispatch(struct event_base *base);
event_base_dispatch會在以下情況停止:
如果 base 中沒有 event,那么事件循環將停止
調用 event_base_loopbreak(),那么事件循環將停止
調用 event_base_loopexit(),那么事件循環將停止
如果出現錯誤,那么事件循環將停止
在事件循環中,會監聽是否存在活躍事件,若存在活躍事件,則調用事件對應的回調函數。
按照上面說的,如果想停止事件循環:
? ? ?1.移除 event_base 中的 event。
? ? ?2.如果想在 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_gettimeofday_cached()(你的系統可能實現 gettimeofday() 為一個系統調用,故用下面函數可避免系統調用的開銷):
// 獲取到的時間為開始執行此輪事件回調函數的時間
// 成功返回 0 失敗返回負數
int event_base_gettimeofday_cached(struct event_base *base,
? ? struct timeval *tv_out);
event事件
event是一組觸發條件,例如:
? ? ?1. 一個文件描述符可讀或者可寫;
? ? ?2. 超時;
? ? ?3. 產生信號;
? ? ?4. 用戶觸發了一個事件。
相關術語:
一個 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 狀態,否則該事件將會被刪除(即該事件是一次性的)。
常用 API:
? ? // 定義了各種條件
? ? // 超時
? ? #define EV_TIMEOUT ? ? ?0x01
? ? // 文件描述符可讀
? ? #define EV_READ ? ? ? ? 0x02
? ? // 文件描述符可寫
? ? #define EV_WRITE ? ? ? ?0x04
? ? // 用于信號檢測
? ? #define EV_SIGNAL ? ? ? 0x08
? ? // 用于指定 event 為 persistent
? ? #define EV_PERSIST ? ? ?0x10
? ? // 用于指定 event 會被邊緣觸發
? ? #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);
信號 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)
注意,一個進程中 Libevent 只能允許一個 event_base 監聽信號。
重用 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
使用 event_assign() 初始化了此 event
event_free() 實際上完成了兩件事情:
調用 event_del() 進行 event 的清理工作
通過內存分配函數在堆上釋放此 event
常用基本數據類型
evutil_socket_t 用于保存 socket
ev_uint64_t 取值范圍 [0, EV_UINT64_MAX]
ev_int64_t 取值范圍 [EV_INT64_MIN, EV_INT64_MAX]
ev_uint32_t 取值范圍 [0, EV_UINT32_MAX]
ev_int32_t 取值范圍 [EV_INT32_MIN, EV_INT32_MAX]
ev_uint16_t 取值范圍 [0, EV_UINT16_MAX]
ev_int16_t 取值范圍 [EV_INT16_MIN, EV_INT16_MAX]
ev_uint8_t 取值范圍 [0, EV_UINT8_MAX]
ev_int8_t 取值范圍 [EV_INT8_MIN, EV_INT8_MAX]
ev_ssize_type(signed size_t)取值范圍 [EV_SSIZE_MIN, EV_SSIZE_MAX]
時間相關
// 用于加或者減前兩個參數,結果被保存在第三個參數中
#define evutil_timeradd(tvp, uvp, vvp) /* ... */
#define evutil_timersub(tvp, uvp, vvp) /* ... */
// 清除 timeval 將其值設置為 0
#define evutil_timerclear(tvp) /* ... */
// 判斷 timeval 是否為 0,如果是 0 返回 false,否則返回 true
#define evutil_timerisset(tvp) /* ... */
// 比較兩個 timeval
// 使用的時候這樣用:
// evutil_timercmp(t1, t2, <=) 含義為判斷 t1 <= t2 是否成立
// cmp 為所有的 C 關系操作符
#define evutil_timercmp(tvp, uvp, cmp)
// 獲取當前時間并保存到 tv
// tz 目前無用
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);
Socket相關
// 用于關閉一個 socket
int evutil_closesocket(evutil_socket_t s);
#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s)
// 返回當前線程的最后一次 socket 操作的錯誤碼
#define EVUTIL_SOCKET_ERROR()
// 改變當前 socket 的錯誤碼
#define EVUTIL_SET_SOCKET_ERROR(errcode)
// 返回特定的 sock 的錯誤碼
#define evutil_socket_geterror(sock)
// 通過 socket 錯誤碼獲取到一個字符串描述
#define evutil_socket_error_to_string(errcode)
// 設置 sock 為非阻塞的 socket
int evutil_make_socket_nonblocking(evutil_socket_t sock);
// 設置 sock 的地址可重用
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);
字符串相關
// 它們對應于標準的 snprintf 和 vsnprintf
int evutil_snprintf(char *buf, size_t buflen, const char *format, ...);
int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);
結語
以上是libevent的基本介紹,在本次的項目中,會使用到最基本的event以及信號event。
對于數據、緩沖區的管理,bufferevent也許用得上,這是后話,需要用到的時候繼續查資料學習!
?
總結
以上是生活随笔為你收集整理的【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【slighttpd】基于lighttp
- 下一篇: 【slighttpd】基于lighttp