【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent
轉(zhuǎn)載地址:https://blog.csdn.net/jiange_zh/article/details/50631393
簡介
由于本項目是純異步的,而對于大量 socket 連接,使用 select 并不高效。(參見我的另一篇博文:epoll簡介)
事實上,大部分系統(tǒng)提供了處理大量 socket 連接的解決方案:
由于各個平臺使用了不同的接口,所以想編寫跨平臺的高性能異步程序就需要做一層跨平臺封裝。此時 Libevent 是一個不錯的選擇(當(dāng)然你也可以自己來實現(xiàn)這個事-。-),其最底層 API(event 和 event_base API)為各個平臺實現(xiàn)高性能異步程序提供了一致的接口。
一些概念
event:會綁定文件描述符、回調(diào)函數(shù)并表示一個或者多個條件(例如,文件描述符可以讀、寫或者超時等)。event 表示的條件如果被觸發(fā)了,那么 event 會變?yōu)榛钴S的,它綁定的回調(diào)函數(shù)就會被執(zhí)行。
event_base: 持有一組 event 并進(jìn)行事件循環(huán),event_base 會存在一個后端(也叫做方法),常見的后端包括 epoll、kqueue 等。
結(jié)構(gòu)
組件:
evutil 用于抽象不同的平臺的網(wǎng)絡(luò)(基礎(chǔ)的)實現(xiàn);
event、event_base 為 Libevent 的核心,為不同的平臺下基于事件的非阻塞 I/O 提供了一套抽象的接口;
bufferevent 對 Libevent 的基于事件的核心的封裝。應(yīng)用程序的讀寫請求是基于緩沖區(qū)的;
evbuffer 為 bufferevent 實現(xiàn)的緩沖區(qū);
evhttp 一個簡單的 HTTP client/server 的實現(xiàn);
evdns 一個簡單的 DNS client/server 的實現(xiàn);
evrpc 一個簡單的 RPC 實現(xiàn);
庫:
libevent_core 包括 util、event_base、evbuffer、bufferevent;
libevent_extra 包括 HTTP、DNS、RPC;
libevent 此庫由于歷史原因而存在,不要使用它;
libevent_pthreads 此庫為基于 pthread 的線程和鎖的實現(xiàn);
libevent_openssl 此庫通過 openssl 和 bufferevent 提供了加密通訊;
頭文件:?
? ? 所有的公用頭文件位于 event2 目錄中。
創(chuàng)建和銷毀 event_base
event_base 是最基本的,故需要首先被創(chuàng)建。
event_base 結(jié)構(gòu)持有一個 event 集合。如果 event_base 被設(shè)置了使用鎖,那么它在多個線程中可以安全的訪問。但是對 event_base 的循環(huán)只能在某個線程中執(zhí)行。如果希望在多個線程中進(jìn)行循環(huán),那么應(yīng)該為每一個線程創(chuàng)建一個 event_base。
event_base 存在多個后端可以選擇(我們也把 event_base 后端叫做 event_base 的方法):
select
poll
epoll
kqueue
devpoll
evport
win32
在創(chuàng)建 event_base 時,libevent 會為我們選擇最快的后端。
創(chuàng)建 event_base 的函數(shù):
// 創(chuàng)建成功返回一個擁有默認(rèn)設(shè)置的 event base
// 創(chuàng)建失敗返回 NULL
struct event_base *event_base_new(void);
// 查看 event_base 實際上使用到的后端
const char *event_base_get_method(const struct event_base *base);
event_base 的釋放使用函數(shù):
void event_base_free(struct event_base *base);
事件循環(huán)(event loop)
event_base 會持有一組 event。如果我們向 event_base 中注冊了一些 event,那么就可以讓 libevent 開始事件循環(huán)了:
// 指定一個 event_base 并開始事件循環(huán)
// 此函數(shù)內(nèi)部被實現(xiàn)為一個不斷進(jìn)行的循環(huán)
// 此函數(shù)返回 0 表示成功退出
// 此函數(shù)返回 -1 表示存在未處理的錯誤
int event_base_dispatch(struct event_base *base);
event_base_dispatch會在以下情況停止:
如果 base 中沒有 event,那么事件循環(huán)將停止
調(diào)用 event_base_loopbreak(),那么事件循環(huán)將停止
調(diào)用 event_base_loopexit(),那么事件循環(huán)將停止
如果出現(xiàn)錯誤,那么事件循環(huán)將停止
在事件循環(huán)中,會監(jiān)聽是否存在活躍事件,若存在活躍事件,則調(diào)用事件對應(yīng)的回調(diào)函數(shù)。
按照上面說的,如果想停止事件循環(huán):
? ? ?1.移除 event_base 中的 event。
? ? ?2.如果想在 event_base 中存在 event 的情況下停止事件循環(huán),可以調(diào)用以下函數(shù):
? ? // 這兩個函數(shù)成功返回 0 失敗返回 -1
? ? // 指定在 tv 時間后停止事件循環(huán)
? ? // 如果 tv == NULL 那么將無延時的停止事件循環(huán)
? ? int event_base_loopexit(struct event_base *base,
? ? ? ? ? ? ? ? ? ? ? ? ? ? const struct timeval *tv);
? ? // 立即停止事件循環(huán)(而不是無延時的停止)
? ? int event_base_loopbreak(struct event_base *base);
event_base_loopexit(base, NULL) :如果當(dāng)前正在為多個活躍事件調(diào)用回調(diào)函數(shù),那么不會立即退出,而是等到所有的活躍事件的回調(diào)函數(shù)都執(zhí)行完成后才退出事件循環(huán);
event_base_loopbreak(base) :如果當(dāng)前正在為多個活躍事件調(diào)用回調(diào)函數(shù),那么當(dāng)前正在調(diào)用的回調(diào)函數(shù)會被執(zhí)行,然后馬上退出事件循環(huán),而并不處理其他的活躍事件了。
在事件的回調(diào)函數(shù)中獲取當(dāng)前的時間可以使用event_base_gettimeofday_cached()(你的系統(tǒng)可能實現(xiàn) gettimeofday() 為一個系統(tǒng)調(diào)用,故用下面函數(shù)可避免系統(tǒng)調(diào)用的開銷):
// 獲取到的時間為開始執(zhí)行此輪事件回調(diào)函數(shù)的時間
// 成功返回 0 失敗返回負(fù)數(shù)
int event_base_gettimeofday_cached(struct event_base *base,
? ? struct timeval *tv_out);
event事件
event是一組觸發(fā)條件,例如:
? ? ?1. 一個文件描述符可讀或者可寫;
? ? ?2. 超時;
? ? ?3. 產(chǎn)生信號;
? ? ?4. 用戶觸發(fā)了一個事件。
相關(guān)術(shù)語:
一個 event 通過 event_new() 創(chuàng)建出來,那么它是已初始化的,另外 event_assign() 也被用來初始化 event(但是有它特定的用法);
一個 event 被注冊到(通過 add 函數(shù)添加到)一個 event_base 中,其為 pending 狀態(tài);
如果 pending event 表示的條件被觸發(fā)了,那么此 event 會變?yōu)榛钴S的,其為 active 狀態(tài),則 event 相關(guān)的回調(diào)函數(shù)被調(diào)用;
event 可以是 persistent(持久的)也可以是非 persistent 的。event 相關(guān)的回調(diào)函數(shù)被調(diào)用之后,只有 persistent event 會繼續(xù)轉(zhuǎn)為 pending 狀態(tài)。對于非 persistent 的 event 你可以在 event 相關(guān)的事件回調(diào)函數(shù)中調(diào)用 event_add() 使得此 event 轉(zhuǎn)為 pending 狀態(tài),否則該事件將會被刪除(即該事件是一次性的)。
常用 API:
? ? // 定義了各種條件
? ? // 超時
? ? #define EV_TIMEOUT ? ? ?0x01
? ? // 文件描述符可讀
? ? #define EV_READ ? ? ? ? 0x02
? ? // 文件描述符可寫
? ? #define EV_WRITE ? ? ? ?0x04
? ? // 用于信號檢測
? ? #define EV_SIGNAL ? ? ? 0x08
? ? // 用于指定 event 為 persistent
? ? #define EV_PERSIST ? ? ?0x10
? ? // 用于指定 event 會被邊緣觸發(fā)
? ? #define EV_ET ? ? ? ? ? 0x20
? ? // event 的回調(diào)函數(shù)
? ? // 參數(shù) evutil_socket_t --- event 關(guān)聯(lián)的文件描述符
? ? // 參數(shù) short --- 當(dāng)前發(fā)生的條件類型
? ? // 參數(shù) void* --- 其為 event_new 函數(shù)中的 arg 參數(shù)
? ? typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
? ? // 創(chuàng)建 event
? ? // base --- 使用此 event 的 event_base
? ? // what --- 指定 event 關(guān)心的條件
? ? // fd --- 文件描述符
? ? // cb --- event 相關(guān)的回調(diào)函數(shù)
? ? // arg --- 用戶自定義數(shù)據(jù)
? ? // 函數(shù)執(zhí)行失敗返回 NULL
? ? struct event *event_new
? ? (struct event_base *base,?
? ? ?evutil_socket_t fd,
? ? ?short what,?
? ? ?event_callback_fn cb,
? ? ?void *arg);
? ? // 釋放 event(真正釋放內(nèi)存,對應(yīng) event_new 使用)
? ? // 可以用來釋放由 event_new 分配的 event
? ? // 若 event 處于 pending 或者 active 狀態(tài)釋放也不會存在問題
? ? void event_free(struct event *event);
? ? // 清理 event(并不是真正釋放內(nèi)存)
? ? // 可用于已經(jīng)初始化的、pending、active 的 event
? ? // 此函數(shù)會將 event 轉(zhuǎn)換為非 pending、非 active 狀態(tài)的
? ? // 函數(shù)返回 0 表示成功 -1 表示失敗
? ? int event_del(struct event *event);
? ? // 用于向 event_base 中注冊 event
? ? // tv 用于指定超時時間,為 NULL 表示無超時時間
? ? // 函數(shù)返回 0 表示成功 -1 表示失敗
? ? int event_add(struct event *ev, const struct timeval *tv);
信號 event 相關(guān)的函數(shù):
?
// base --- event_base
// signum --- 信號,例如 SIGHUP
// callback --- 信號出現(xiàn)時調(diào)用的回調(diào)函數(shù)
// arg --- 用戶自定義數(shù)據(jù)
#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)
注意,一個進(jìn)程中 Libevent 只能允許一個 event_base 監(jiān)聽信號。
重用 event ,避免 event 在堆上的頻繁分配和釋放:
? ? // 此函數(shù)用于初始化 event(包括可以初始化棧上和靜態(tài)存儲區(qū)中的 event)
? ? // event_assign() 和 event_new() 除了 event 參數(shù)之外,使用了一樣的參數(shù)
? ? // event 參數(shù)用于指定一個未初始化的且需要初始化的 event
? ? // 函數(shù)成功返回 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);
? ? // 類似上面的函數(shù),此函數(shù)被信號 event 使用
? ? #define evsignal_assign(event, base, signum, callback, arg) \
? ? ? ? event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)
已經(jīng)初始化或者處于 pending 的 event,首先需要調(diào)用 event_del() 后才能調(diào)用 event_assign()。
event_new() 實際上完成了兩件事情:
通過內(nèi)存分配函數(shù)在堆上分配了 event
使用 event_assign() 初始化了此 event
event_free() 實際上完成了兩件事情:
調(diào)用 event_del() 進(jìn)行 event 的清理工作
通過內(nèi)存分配函數(shù)在堆上釋放此 event
常用基本數(shù)據(jù)類型
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]
時間相關(guān)
// 用于加或者減前兩個參數(shù),結(jié)果被保存在第三個參數(shù)中
#define evutil_timeradd(tvp, uvp, vvp) /* ... */
#define evutil_timersub(tvp, uvp, vvp) /* ... */
// 清除 timeval 將其值設(shè)置為 0
#define evutil_timerclear(tvp) /* ... */
// 判斷 timeval 是否為 0,如果是 0 返回 false,否則返回 true
#define evutil_timerisset(tvp) /* ... */
// 比較兩個 timeval
// 使用的時候這樣用:
// evutil_timercmp(t1, t2, <=) 含義為判斷 t1 <= t2 是否成立
// cmp 為所有的 C 關(guān)系操作符
#define evutil_timercmp(tvp, uvp, cmp)
// 獲取當(dāng)前時間并保存到 tv
// tz 目前無用
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);
Socket相關(guān)
// 用于關(guān)閉一個 socket
int evutil_closesocket(evutil_socket_t s);
#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s)
// 返回當(dāng)前線程的最后一次 socket 操作的錯誤碼
#define EVUTIL_SOCKET_ERROR()
// 改變當(dāng)前 socket 的錯誤碼
#define EVUTIL_SET_SOCKET_ERROR(errcode)
// 返回特定的 sock 的錯誤碼
#define evutil_socket_geterror(sock)
// 通過 socket 錯誤碼獲取到一個字符串描述
#define evutil_socket_error_to_string(errcode)
// 設(shè)置 sock 為非阻塞的 socket
int evutil_make_socket_nonblocking(evutil_socket_t sock);
// 設(shè)置 sock 的地址可重用
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);
字符串相關(guān)
// 它們對應(yīng)于標(biāo)準(zhǔn)的 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);
結(jié)語
以上是libevent的基本介紹,在本次的項目中,會使用到最基本的event以及信號event。
對于數(shù)據(jù)、緩沖區(qū)的管理,bufferevent也許用得上,這是后話,需要用到的時候繼續(xù)查資料學(xué)習(xí)!
?
總結(jié)
以上是生活随笔為你收集整理的【slighttpd】基于lighttpd架构的Server项目实战(2)—预备知识之libevent的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【slighttpd】基于lighttp
- 下一篇: 【slighttpd】基于lighttp