Libevent 2 提供了 bufferevent 接口,簡化了編程的難度,bufferevent 其實是對底層事件核心的封裝,所以學習 bufferevent 的實現是研究 Libevent 底層 event、event_base 用法的一個好辦法。本文假定你已經對 Libevent 有必定的認識,不然能夠先閱讀我關於 Libevent 的介紹:
Libevent(1)— 簡介、編譯、配置
Libevent(2)— event、event_base
Libevent(3)— 基礎庫
Libevent(4)— Bufferevent
Libevent(5)— 鏈接監聽器html
參考文獻列表:
http://www.wangafu.net/~nickm/libevent-book/編程
此文編寫的時候,使用到的 Libevent 爲 2.0.21。本文略過了關於 event 優先權和超時相關的討論。ubuntu
建立和銷燬 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 開始事件循環了:socket
- // 指定一個 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();
// 這裏假定 fd一、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
創建鏈接:
// address 和 addrlen 和標準的 connect 函數的參數沒有區別
// 若是 bufferevent bev 沒有設置 socket(在建立時能夠設置 socket)
// 那麼調用此函數將分配一個新的 socket 給 bev
// 鏈接成功返回 0 失敗返回 -1
int bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *address, int addrlen);
簡單的一個範例:
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <sys/socket.h>
#include <string.h>
// 事件回調函數
void eventcb(struct bufferevent *bev, short events, void *ptr)
{
// 鏈接成功創建
if (events & BEV_EVENT_CONNECTED) {
/* We're connected to 127.0.0.1:8080. Ordinarily we'd do
something here, like start reading or writing. */
// 出現錯誤
} else if (events & BEV_EVENT_ERROR) {
/* An error occured while connecting. */
}
}
int main_loop(void)
{
struct event_base *base;
struct bufferevent *bev;
struct sockaddr_in sin;
base = event_base_new();
// 初始化鏈接地址
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
sin.sin_port = htons(8080); /* Port 8080 */
// 建立一個基於 socket 的 bufferevent
// 參數 -1 表示並不爲此 bufferevent 設置 socket
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
// 爲 bufferevent bev 設置回調函數
// 這裏僅僅設置了事件回調函數
// 後面會詳細談及此函數
bufferevent_setcb(bev, NULL, NULL, eventcb, NULL); //setcb cb表示callback,回調
// 進行鏈接
if (bufferevent_socket_connect(bev,
(struct sockaddr *)&sin, sizeof(sin)) < 0) {
/* Error starting connection */
bufferevent_free(bev);
return -1;
}
// 開始事件循環
event_base_dispatch(base);
return 0;
}
更多的 bufferevent API
釋放 bufferevent
- // 若是存在未完成的延時回調,bufferevent 會在回調完成後才被真正釋放
- void bufferevent_free(struct bufferevent *bev);
bufferevent 回調函數的設置和獲取
/ 讀取、寫入回調函數原型
// ctx 爲用戶自定義數據(由 bufferevent_setcb 設定)
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
// 事件回調函數原型
// ctx 爲用戶自定義數據(由 bufferevent_setcb 設定)
// events 選項能夠爲:
// BEV_EVENT_READING --- 在 bufferevent 上進行讀取操做時出現了一個事件
// BEV_EVENT_WRITING --- 在 bufferevent 上進行寫入操做時出現了一個事件
// BEV_EVENT_ERROR --- 進行 bufferevent 操做時(例如調用 bufferevent API)出錯,獲取詳細的錯誤信息使用 EVUTIL_SOCKET_ERROR()
// BEV_EVENT_TIMEOUT --- 在 bufferevent 上出現了超時
// BEV_EVENT_EOF --- 在 bufferevent 上遇到了文件結束符
// BEV_EVENT_CONNECTED --- 在 bufferevent 上請求鏈接完成了
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
// 設置回調函數
// 若是但願禁用回調函數,那麼設置對應的參數爲 NULL
void bufferevent_setcb(
// bufferevent
struct bufferevent *bufev,
// 讀取回調函數
bufferevent_data_cb readcb,
// 寫入回調函數
bufferevent_data_cb writecb,
// 事件回調函數
bufferevent_event_cb eventcb,
// 用戶定義的數據
// 這三個回調函數均共享此參數
void *cbarg
);
// 取回回調函數
// 參數爲 NULL 表示忽略
void bufferevent_getcb(
struct bufferevent *bufev,
bufferevent_data_cb *readcb_ptr,
bufferevent_data_cb *writecb_ptr,
bufferevent_event_cb *eventcb_ptr,
void **cbarg_ptr
);
設置 watermark
- // events 參數能夠爲
- // EV_READ 表示設置 read watermark
- // EV_WRITE 表示設置 write watermark
- // EV_READ | EV_WRITE 表示設置 read 以及 write watermark
- void bufferevent_setwatermark(struct bufferevent *bufev, short events,
- size_t lowmark, size_t highmark);
獲取到輸入和輸出 buffer
- // 獲取到輸入 buffer
- struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
- // 獲取到輸出 buffer
- struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
添加數據到輸出 buffer
- // 下面兩個函數執行成功返回 0 失敗返回 -1
-
- // 向 bufev 的輸出 buffer 中添加大小爲 size 的數據
- // 數據的首地址爲 data
- int bufferevent_write(struct bufferevent *bufev,
- const void *data, size_t size);
- // 向 bufev 的輸出 buffer 中添加數據
- // 數據來源於 buf
- // 此函數會清除 buf 中的全部數據
- int bufferevent_write_buffer(struct bufferevent *bufev,
- struct evbuffer *buf);
從輸入 buffer 中獲取數據
- // 從 bufev 的輸入 buffer 中獲取最多 size 字節的數據保存在 data 指向的內存中
- // 此函數返回實際讀取的字節數
- size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
- // 獲取 bufev 的輸入 buffer 中的全部數據並保存在 buf 中
- // 此函數成功返回 0 失敗返回 -1
- int bufferevent_read_buffer(struct bufferevent *bufev,
- struct evbuffer *buf);
關於 bufferevent 的一些高級話題,能夠參考:http://www.wangafu.net/~nickm/libevent-book/Ref6a_advanced_bufferevents.html
evbuffers
evbuffer 是一個隊列,在其尾部添加數據和在其頭部刪除數據均被優化了。evbuffer 相關的 API 在這裏能夠查看:http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html
參考文獻列表:
http://www.wangafu.net/~nickm/libevent-book/
此文編寫的時候,使用到的 Libevent 爲 2.0.21
Libevent 提供了鏈接監聽器 evconnlistener
建立 evconnlistener 實例
// 鏈接監聽器回調函數原型
typedef void (*evconnlistener_cb)(
struct evconnlistener *listener,
// 新的 socket
evutil_socket_t sock,
// 新的 socket 對應的地址
struct sockaddr *addr,
int len,
// 用戶自定義數據
void *ptr
);
// 建立一個新的鏈接監聽器
struct evconnlistener *evconnlistener_new(
struct event_base *base,
// 一個新的鏈接到來時此回調被調用
evconnlistener_cb cb,
// 用戶自定義數據,會被傳遞給 cb 回調函數
void *ptr,
// 鏈接監聽器的選項(下面會詳細談到)
unsigned flags,
// 爲標準的 listen 函數的 backlog 參數
// 若是爲負數,Libevent 將嘗試選擇一個合適的值
int backlog,
// socket
// Libevent 假定此 socket 已經綁定
evutil_socket_t fd
);
// 建立一個新的鏈接監聽器
// 大多數參數含義同於 evconnlistener_new
struct evconnlistener *evconnlistener_new_bind(
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags,
int backlog,
// 指定須要綁定的 socket 地址
const struct sockaddr *sa,
int socklen
);
鏈接監聽器的經常使用選項以下:
- LEV_OPT_CLOSE_ON_FREE
當關閉鏈接監聽器其底層 socket 也被自動釋放
- LEV_OPT_REUSEABLE
設置 socket 綁定的地址能夠重用
- LEV_OPT_THREADSAFE
設置鏈接監聽器爲線程安全的
釋放鏈接監聽器
- void evconnlistener_free(struct evconnlistener *lev);
錯誤檢測
若是鏈接監聽器出錯,咱們能夠獲得通知:
- // 鏈接監聽器錯誤回調函數原型
- typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr);
-
- // 爲鏈接監聽器設置錯誤回調函數
- void evconnlistener_set_error_cb(struct evconnlistener *lev,
- evconnlistener_errorcb errorcb);
一個詳細的範例(echo 服務器)
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
// 讀取回調函數
static void
echo_read_cb(struct bufferevent *bev, void *ctx)
{
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev);
// 將輸入緩衝區的數據直接拷貝到輸出緩衝區
evbuffer_add_buffer(output, input);
}
// 事件回調函數
static void
echo_event_cb(struct bufferevent *bev, short events, void *ctx)
{
if (events & BEV_EVENT_ERROR)
perror("Error from bufferevent");
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_free(bev);
}
}
// 鏈接監聽器回調函數
static void
accept_conn_cb(struct evconnlistener *listener,
evutil_socket_t fd, struct sockaddr *address, int socklen,
void *ctx)
{
// 爲新的鏈接分配並設置 bufferevent
struct event_base *base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(
base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);
bufferevent_enable(bev, EV_READ|EV_WRITE);
}
// 鏈接監聽器錯誤回調函數
static void
accept_error_cb(struct evconnlistener *listener, void *ctx)
{
struct event_base *base = evconnlistener_get_base(listener);
// 獲取到錯誤信息
int err = EVUTIL_SOCKET_ERROR();
fprintf(stderr, "Got an error %d (%s) on the listener. "
"Shutting down.\n", err, evutil_socket_error_to_string(err));
// 退出事件循環
event_base_loopexit(base, NULL);
}
int
main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct sockaddr_in sin;
int port = 9876;
if (argc > 1) {
port = atoi(argv[1]);
}
if (port<=0 || port>65535) {
puts("Invalid port");
return 1;
}
base = event_base_new();
if (!base) {
puts("Couldn't open event base");
return 1;
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(0);
sin.sin_port = htons(port);
listener = evconnlistener_new_bind(base, accept_conn_cb, NULL,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1,
(struct sockaddr*) & sin, sizeof(sin));
if (!listener) {
perror("Couldn't create listener");
return 1;
}
evconnlistener_set_error_cb(listener, accept_error_cb);
event_base_dispatch(base);
return 0;
}
ubuntu下頭文件:
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<netinet/in.h>
#include<event2/listener.h>
#include<event2/bufferevent.h>
#include<event2/buffer.h>
關於echo 的讀的客戶端看:
http://blog.sina.com.cn/s/blog_87d946d40100vrv9.html
Libevent(3)— 基礎庫
參考文獻列表:
http://www.wangafu.net/~nickm/libevent-book/
此文編寫的時候,使用到的 Libevent 爲 2.0.21
經常使用基本數據類型
時間相關
Socket API
字符串相關
安全的隨機數生成
Libevent(4)— Bufferevent
此文編寫的時候,使用到的 Libevent 爲 2.0.21
Buffer IO 模式
bufferevent 提供給咱們一種 Buffer IO 模式(這裏以寫入數據爲例):
每個 bufferevent 都包含了一個輸入 buffer 和一個輸出 buffer,它們的類型爲 evbuffer(結構體)。當咱們向 bufferevent 寫入數據的時候,實際上數據首先被寫入到了輸出 buffer,當 bufferevent 有數據可讀時,咱們其實是從輸入 buffer 中獲取數據。
目前 bufferevent 目前僅僅支持 stream-oriented 的協議(例如 TCP)並不支持 datagram-oriented 協議(例如 UDP)。一個 bufferevent 的實例負責一個特定的鏈接上的數據收發。
Libevent 能夠按須要建立多種類型的 bufferevent:
bufferevent 的回調函數
每一個 bufferevent 實例能夠有 3 個回調函數(經過接口 bufferevent_setcb 設置):
對於 buffer 的讀、寫和回調行爲能夠經過幾個參數來配置,這幾個參數在 Libevent 中被叫作 watermark(水位標記,咱們能夠將 buffer 想象爲一個水池,水位標記用於標記水池中水的多少,也就是說,watermark 用於標記 buffer 中的數據量)。watermark 被實現爲整數(類型爲 size_t),有幾種類型的 watermark:
在一些特殊需求中(詳細並不討論),咱們可能須要回調函數被延時執行(這種被延時的回調函數被叫作 Deferred callbacks)。延時回調函數會在事件循環中排隊,並在普通事件回調函數(regular event’s callback)以後被調用。
從基於 socket 的 bufferevent 開始認識 bufferevent
建立基於 socket 的 bufferevent:
buffervent 的選項能夠用來改變 bufferevent 的行爲。可用的選項包括:
當 bufferevent 被釋放同時關閉底層(socket 被關閉等)
爲 bufferevent 自動分配鎖,這樣可以在多線程環境中安全使用
當設置了此標誌,bufferevent 會延遲它的全部回調(參考前面說的延時回調)
若是 bufferevent 被設置爲線程安全的,用戶提供的回調被調用時 bufferevent 的鎖會被持有
若是設置了此選項,Libevent 將在調用你的回調時釋放 bufferevent 的鎖