libevent系列文章

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

Libevent(2)— event、event_base

參考文獻列表:
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 的方法):安全

  1. select
  2. poll
  3. epoll
  4. kqueue
  5. devpoll
  6. evport
  7. win32

在咱們建立 event_base 的時候,Libevent 會爲咱們選擇最快的後端。建立 event_base 經過函數 event_base_new() 來完成:服務器

  1. // 建立成功返回一個擁有默認設置的 event base
  2. // 建立失敗返回 NULL
  3. struct event_base *event_base_new(void);
  4.  
  5. // 查看 event_base 實際上使用到的後端
  6. const char *event_base_get_method(const struct event_base *base);

對於大多數程序來講,默認設置已經夠用了。多線程

event_base 的釋放使用函數:異步

    1. void event_base_free(struct event_base *base);

 

事件循環(event loop)

event_base 會持有一組 event(這是咱們前面說到的),換而言之就是說,咱們能夠向 event_base 中註冊 event(具體如何註冊本文先不談)。若是咱們向 event_base 中註冊了一些 event,那麼就可讓 Libevent 開始事件循環了:socket

  1. // 指定一個 event_base 並開始事件循環
  2. // 此函數內部被實現爲一個不斷進行的循環
  3. // 此函數返回 0 表示成功退出
  4. // 此函數返回 -1 表示存在未處理的錯誤
  5. int event_base_dispatch(struct event_base *base);

默認的事件循環會在如下的狀況中止(也就是 event_base_dispatch 會返回):函數

  1. 若是 base 中沒有 event,那麼事件循環將中止
  2. 調用 event_base_loopbreak(),那麼事件循環將中止
  3. 調用 event_base_loopexit(),那麼事件循環將中止
  4. 若是出現錯誤,那麼事件循環將中止

事件循環會檢測是否存在活躍事件(以前已經介紹過活躍事件這一術語:http://name5566.com/4190.html),若存在活躍事件,那麼調用事件對應的回調函數。

中止事件循環的能夠經過移除 event_base 中的 event 來實現。若是你但願在 event_base 中存在 event 的狀況下中止事件循環,能夠經過如下函數完成:

  1. // 這兩個函數成功返回 0 失敗返回 -1
  2. // 指定在 tv 時間後中止事件循環
  3. // 若是 tv == NULL 那麼將無延時的中止事件循環
  4. int event_base_loopexit(struct event_base *base,
  5. const struct timeval *tv);
  6. // 當即中止事件循環(而不是無延時的中止)
  7. int event_base_loopbreak(struct event_base *base);

這裏須要區別一下 event_base_loopexit(base, NULL) 和 event_base_loopbreak(base):

  1. event_base_loopexit(base, NULL) 若是當前正在爲多個活躍事件調用回調函數,那麼不會當即退出,而是等到全部的活躍事件的回調函數都執行完成後才退出事件循環
  2. event_base_loopbreak(base) 若是當前正在爲多個活躍事件調用回調函數,那麼當前正在調用的回調函數會被執行,而後立刻退出事件循環,而並不處理其餘的活躍事件了

有時候,咱們須要在事件的回調函數中獲取當前的時間,這時候你不須要調用 gettimeofday() 而是使用 event_base_gettimeofday_cached()(你的系統可能實現 gettimeofday() 爲一個系統調用,你能夠避免系統調用的開銷):

  1. // 獲取到的時間爲開始執行此輪事件回調函數的時間
  2. // 成功返回 0 失敗返回負數
  3. int event_base_gettimeofday_cached(struct event_base *base,
  4. struct timeval *tv_out);

若是咱們須要(爲了調試)獲取被註冊到 event_base 的全部的 event 和它們的狀態,調用 event_base_dump_events() 函數:

  1. // f 表示輸出內容的目標文件
  2. void event_base_dump_events(struct event_base *base, FILE *f);

event

如今開始詳細的討論一下 event。在 Libevent 中 event 表示了一組條件,例如:

  1. 一個文件描述符可讀或者可寫
  2. 超時
  3. 出現一個信號
  4. 用戶觸發了一個事件

event 的相關術語:

  1. 一個 event 經過 event_new() 建立出來,那麼它是已初始化的,另外 event_assign() 也被用來初始化 event(可是有它特定的用法)
  2. 一個 event 被註冊到(經過 add 函數添加到)一個 event_base 中,其爲 pending 狀態
  3. 若是 pending event 表示的條件被觸發了,那麼此 event 會變爲活躍的,其爲 active 狀態,則 event 相關的回調函數被調用
  4. event 能夠是 persistent(持久的)也能夠是非 persistent 的。event 相關的回調函數被調用以後,只有 persistent event 會繼續轉爲 pending 狀態。對於非 persistent 的 event 你能夠在 event 相關的事件回調函數中調用 event_add() 使得此 event 轉爲 pending 狀態

event 經常使用 API:

  1.  
    // 定義了各類條件
    // 超時
    #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);

     

一個範例:

  1.  
    #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 相關的函數:

  1. // base --- event_base
  2. // signum --- 信號,例如 SIGHUP
  3. // callback --- 信號出現時調用的回調函數
  4. // arg --- 用戶自定義數據
  5. #define evsignal_new(base, signum, callback, arg) \
  6. event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
  7.  
  8. // 將信號 event 註冊到 event_base
  9. #define evsignal_add(ev, tv) \
  10. event_add((ev),(tv))
  11.  
  12. // 清理信號 event
  13. #define evsignal_del(ev) \
  14. event_del(ev)

在一般的 POSIX 信號處理函數中,很多函數是不能被調用的(例如,不可重入的函數),可是在 Libevent 中卻沒有這些限制,由於信號 event 設定的回調函數運行在事件循環中。另外須要注意的是,在同一個進程中 Libevent 只能容許一個 event_base 監聽信號。

性能相關問題

Libevent 提供了咱們機制來重用 event 用以免 event 在堆上的頻繁分配和釋放。相關的接口:

  1. // 此函數用於初始化 event(包括能夠初始化棧上和靜態存儲區中的 event)
  2. // event_assign() 和 event_new() 除了 event 參數以外,使用了同樣的參數
  3. // event 參數用於指定一個未初始化的且須要初始化的 event
  4. // 函數成功返回 0 失敗返回 -1
  5. int event_assign(struct event *event, struct event_base *base,
  6. evutil_socket_t fd, short what,
  7. void (*callback)(evutil_socket_t, short, void *), void *arg);
  8.  
  9. // 相似上面的函數,此函數被信號 event 使用
  10. #define evsignal_assign(event, base, signum, callback, arg) \
  11. 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() 實際上完成了兩件事情:

  1. 經過內存分配函數在堆上分配了 event
  2. 使用 event_assign() 初始化了此 event

event_free() 實際上完成了兩件事情:

  1. 調用 event_del() 進行 event 的清理工做
  2. 經過內存分配函數在堆上釋放此 event

 

Libevent(3)— 基礎庫

參考文獻列表:
http://www.wangafu.net/~nickm/libevent-book/

此文編寫的時候,使用到的 Libevent 爲 2.0.21

經常使用基本數據類型

  1. evutil_socket_t 用於保存 socket
  2. ev_uint64_t 取值範圍 [0, EV_UINT64_MAX]
  3. ev_int64_t 取值範圍 [EV_INT64_MIN, EV_INT64_MAX]
  4. ev_uint32_t 取值範圍 [0, EV_UINT32_MAX]
  5. ev_int32_t 取值範圍 [EV_INT32_MIN, EV_INT32_MAX]
  6. ev_uint16_t 取值範圍 [0, EV_UINT16_MAX]
  7. ev_int16_t 取值範圍 [EV_INT16_MIN, EV_INT16_MAX]
  8. ev_uint8_t 取值範圍 [0, EV_UINT8_MAX]
  9. ev_int8_t 取值範圍 [EV_INT8_MIN, EV_INT8_MAX]
  10. 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 API

// 用於關閉一個 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);

字符串相關

  1. // 它們對應於標準的 snprintf 和 vsnprintf
  2. int evutil_snprintf(char *buf, size_t buflen, const char *format, ...);
  3. int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);

安全的隨機數生成

  1. // 此函數將使用隨機的數據填充 n 個字節的 buf
  2. void evutil_secure_rng_get_bytes(void *buf, size_t n);

 

Libevent(4)— Bufferevent

此文編寫的時候,使用到的 Libevent 爲 2.0.21

Buffer IO 模式

bufferevent 提供給咱們一種 Buffer IO 模式(這裏以寫入數據爲例):

  1. 在咱們須要經過某個鏈接發送數據的時候,先將等待發送的數據放入到一個 buffer 中
  2. 等待此鏈接能夠寫入數據
  3. 儘量多的獲取 buffer 中的數據寫入此鏈接
  4. 若是 buffer 中還有須要寫入的數據則繼續等待直到此鏈接能夠寫入數據

每個 bufferevent 都包含了一個輸入 buffer 和一個輸出 buffer,它們的類型爲 evbuffer(結構體)。當咱們向 bufferevent 寫入數據的時候,實際上數據首先被寫入到了輸出 buffer,當 bufferevent 有數據可讀時,咱們其實是從輸入 buffer 中獲取數據。

目前 bufferevent 目前僅僅支持 stream-oriented 的協議(例如 TCP)並不支持 datagram-oriented 協議(例如 UDP)。一個 bufferevent 的實例負責一個特定的鏈接上的數據收發。

Libevent 能夠按須要建立多種類型的 bufferevent:

  1. 基於 socket 的 bufferevent。此類型的 bufferevent 使用 socket 來進行數據的收發,使用 event 機制來判斷 socket 是否能夠進行讀寫操做
  2. 異步 IO bufferevent。此類型的 bufferevent 使用 IOCP 接口實現(僅 Windows 下可用且目前處於實驗階段)
  3. Filtering bufferevent。此類型的 bufferevent 能夠在同底層交互時完成一些額外的數據處理工做,例如能夠完成數據的壓縮和解析工做。這種類型的 bufferevent 的一個實例會封裝了另外的一個 bufferevent,咱們把這個被封裝的 bufferevent 叫作底層 bufferevent
  4. Paired bufferevent。本文不談

bufferevent 的回調函數

每一個 bufferevent 實例能夠有 3 個回調函數(經過接口 bufferevent_setcb 設置):

  1. 讀取回調函數。默認狀況下,只要從底層讀取到了數據此回調函數將被調用
  2. 寫入回調函數。默認狀況下,足夠多的數據被寫入底層此回調函數將被調用
  3. 事件回調函數。當某些事件(錯誤)發生時被調用

對於 buffer 的讀、寫和回調行爲能夠經過幾個參數來配置,這幾個參數在 Libevent 中被叫作 watermark(水位標記,咱們能夠將 buffer 想象爲一個水池,水位標記用於標記水池中水的多少,也就是說,watermark 用於標記 buffer 中的數據量)。watermark 被實現爲整數(類型爲 size_t),有幾種類型的 watermark:

  1. Read low-water mark 用於控制讀取回調函數的行爲。當 bufferevent 進行讀取操做時,Read low-water mark 的值決定了輸入 buffer 有多少數據後調用讀取回調函數。默認的狀況下,此值爲 0,所以 bufferevent 讀取操做都會致使讀取回調函數被調用
  2. Read high-water mark 用於控制輸入 buffer 的大小。若是輸入 buffer 中的數據量達到 Read high-water mark 的值,那麼 bufferevent 將中止讀取。默認的狀況下,此值爲無限大
  3. Write low-water mark,用於控制寫入回調函數的行爲。當 bufferevent 進行寫入操做時,Write low-water mark 的值決定了輸出 buffer 有多少數據後調用寫入回調函數。默認的狀況下,此值爲 0,所以寫入回調函數會在輸出 buffer 爲空的時候被調用
  4. Write high-water mark,此值在使用 Filtering bufferevent 有特殊的用途

在一些特殊需求中(詳細並不討論),咱們可能須要回調函數被延時執行(這種被延時的回調函數被叫作 Deferred callbacks)。延時回調函數會在事件循環中排隊,並在普通事件回調函數(regular event’s callback)以後被調用。

從基於 socket 的 bufferevent 開始認識 bufferevent

建立基於 socket 的 bufferevent:

// 建立一個基於 socket 的 bufferevent
// 函數執行失敗返回 NULL
struct bufferevent *bufferevent_socket_new(
    struct event_base *base,
    // socket 文件描述符
    // 此 socket 必須被設置爲非阻塞的
    // 能夠設置爲 -1 表示以後再設置
    evutil_socket_t fd,
    // bufferevent 的選項
    enum bufferevent_options options);

buffervent 的選項能夠用來改變 bufferevent 的行爲。可用的選項包括:

    1. BEV_OPT_CLOSE_ON_FREE
      當 bufferevent 被釋放同時關閉底層(socket 被關閉等)
    2. BEV_OPT_THREADSAFE
      爲 bufferevent 自動分配鎖,這樣可以在多線程環境中安全使用
    3. BEV_OPT_DEFER_CALLBACKS
      當設置了此標誌,bufferevent 會延遲它的全部回調(參考前面說的延時回調)
    4. BEV_OPT_UNLOCK_CALLBACKS
      若是 bufferevent 被設置爲線程安全的,用戶提供的回調被調用時 bufferevent 的鎖會被持有
      若是設置了此選項,Libevent 將在調用你的回調時釋放 bufferevent 的鎖

創建鏈接:

// 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

  1. // 若是存在未完成的延時回調,bufferevent 會在回調完成後才被真正釋放
  2. 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

  1. // events 參數能夠爲
  2. // EV_READ 表示設置 read watermark
  3. // EV_WRITE 表示設置 write watermark
  4. // EV_READ | EV_WRITE 表示設置 read 以及 write watermark
  5. void bufferevent_setwatermark(struct bufferevent *bufev, short events,
  6. size_t lowmark, size_t highmark);

獲取到輸入和輸出 buffer

  1. // 獲取到輸入 buffer
  2. struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
  3. // 獲取到輸出 buffer
  4. struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);

添加數據到輸出 buffer

  1. // 下面兩個函數執行成功返回 0 失敗返回 -1
  2.  
  3. // 向 bufev 的輸出 buffer 中添加大小爲 size 的數據
  4. // 數據的首地址爲 data
  5. int bufferevent_write(struct bufferevent *bufev,
  6. const void *data, size_t size);
  7. // 向 bufev 的輸出 buffer 中添加數據
  8. // 數據來源於 buf
  9. // 此函數會清除 buf 中的全部數據
  10. int bufferevent_write_buffer(struct bufferevent *bufev,
  11. struct evbuffer *buf);

從輸入 buffer 中獲取數據

  1. // 從 bufev 的輸入 buffer 中獲取最多 size 字節的數據保存在 data 指向的內存中
  2. // 此函數返回實際讀取的字節數
  3. size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
  4. // 獲取 bufev 的輸入 buffer 中的全部數據並保存在 buf 中
  5. // 此函數成功返回 0 失敗返回 -1
  6. int bufferevent_read_buffer(struct bufferevent *bufev,
  7. 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

Libevent(5)— 鏈接監聽器

參考文獻列表:
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
);

鏈接監聽器的經常使用選項以下:

  1. LEV_OPT_CLOSE_ON_FREE
    當關閉鏈接監聽器其底層 socket 也被自動釋放
  2. LEV_OPT_REUSEABLE
    設置 socket 綁定的地址能夠重用
  3. LEV_OPT_THREADSAFE
    設置鏈接監聽器爲線程安全的

釋放鏈接監聽器

  1. void evconnlistener_free(struct evconnlistener *lev);

錯誤檢測
若是鏈接監聽器出錯,咱們能夠獲得通知:

  1. // 鏈接監聽器錯誤回調函數原型
  2. typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr);
  3.  
  4. // 爲鏈接監聽器設置錯誤回調函數
  5. void evconnlistener_set_error_cb(struct evconnlistener *lev,
  6. 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

相關文章
相關標籤/搜索