Libevent 官方文件學習筆記(2. bufferevent部分)

amc發表於2019-05-10

本文地址:https://segmentfault.com/a/1190000005601925


Libevent的輔助函式和資料型別

標頭檔案是<event2/util.h>。以下只列出我自己會用到的部分。

基本型別

evutil_socket_t
Socket的抽象。除了Windows之外,其他系統都是一個int型別。如果考慮Windows的相容性的話,建議用這個型別。

標準整型

以下是幾種資料長度的定義

    ----------------------------------------------------------
       Type         位寬   符號數     最大值          最小值
    ----------------------------------------------------------
    ev_uint64_t      64      x    EV_UINT64_MAX        0
    ev_int64_t       64      √    EV_INT64_MAX    EV_INT64_MIN
    ev_uint32_t      32      x    EV_UINT32_MAX        0
    ev_int32_t       32      √    EV_INT32_MAX    EV_INT32_MIN
    ev_uint16_t      16      x    EV_UINT16_MAX        0
    ev_int16_t       16      √    EV_INT16_MAX    EV_INT16_MIN
    ev_uint8_t       8       x    EV_UINT8_MAX         0
    ev_int8_t        8       √    EV_INT8_MAX     EV_INT8_MIN

其他一些型別
ev_ssize_t
ev_off_t

適配函式的巨集

#define evutili_timer_add(tvp, uvp, vvp)
#define evutili_timer_sub(tvp, uvp, vvp)

計算timeval資料加減的巨集,vvp = tvp +/- uvp。注意三者都要使用指標

#define evutil_timerclear(tvp)
#define evutil_timerisset(tvp)

將timeval清零,或者判斷是否被清零

#define evutil_timercmp(tvp, uvp, cmp)
判斷timeval的先後,其中cmp是比較富豪,比如==, <=, >=, <, >, !=

int evutil_gettimeofday (struct timeval *tv, struct timezone *tz);

Socket相關的函式

#define evutil_socket_geterror (sock)
#define evutil_socket_error_to_string (errcode)

獲得指定socket的error code,以及轉為可讀的string

int evutil_make_socket_nonblocking (evutil_sopcket_t sock);

將一個socket非阻塞。

字串操作

ev_int64_t evutil_strtoll (const char *s, char **endptr, int base);
int evutil_snprintf (char *but, size_t buflen, const char *format, ...);
int evutil_vsnprintf (char *bug, size_t buflen, const char *format, va_list ap);

資料結構體

#define evutil_offsetof (type, field)

Bufferevent:概念和基本知識

傳統的libevent使用方法:

  1. 當需要放資料的時候,存入資料到buffer
  2. 等待socket可寫
  3. 儘量向socket中寫更多的data
  4. 如果還有data未寫入,則再等待socket可寫

使用標頭檔案<event2/bufferevent.h>可以使用bufferevent,節省read/write呼叫,只需要將資料放入/取出一個buffer即可
  目前bufferevent只支援TCP,未來可能支援UDP
  每個bufferevent有一個read buffer和一個write buffer,都是struct evbuffer。這個後文再講。

回撥和bufferevent

Bufferevent使用叫做watermarks(水位線)的東西來定義回撥函式的呼叫時機。有以下幾個watermarks:
  Read low-water mark:當read buffer的量大於等於這麼多時,呼叫callback。預設是0,即一有資料就回撥。
  Read high-water mark:當read buffer的量大於等於這麼多時,停止read,直到buffer裡面的資料低於這個值為止,重新開始read。預設是無限。
  Write low-water mark:當write buffer的量小於等於這麼多時,呼叫回撥。預設是0
  Write high-water mark:bufferevent未直接使用這個值。參見後文

Bufferevent也有錯誤回撥事件回撥,用於告知一些非資料時間和錯誤。如下:
  BEV_EVENT_READING
  BEV_EVENT_WRITING
  BEV_EVENT_ERROR:操作發生錯誤。需要呼叫EVUTIL_SOCKET_ERROR()來判斷出現了什麼錯誤
  BEV_EVENT_TIMEOUT
  BEV_EVENT_EOF
  BEV_EVENT_CONNECTED:請求連線已經完成

延遲迴調
一般情況下,bufferevent的callback時立刻呼叫的。但是如果呼叫關係很複雜的話可能會出bug,這個時候可以將bufferevent設定為延遲的(defered),這樣會使得回撥函式放在event loop中被執行,單執行緒。

Bufferevent的選項

BEV_OPT_CLOSE_ON_FREE:當bufferevent釋放時,關閉底層傳輸
BEV_OPT_THREADSAFE:為bufferevent使用lock
BEV_OPT_DEFER_CALLBACKS:將callback設為延遲的
BEV_OPT_UNLOCK_CALLBACKS:預設情況下如果有THREADSAFE標誌,呼叫callback時會加鎖。使用這個標誌是的即便有THREADSAFE標誌,呼叫callback也不加鎖

使用基於socket的bufferevent

struct bufferevent *bufferevent_socket_new (
                        struct event_base *base,
                        evutil_socket_t    fd,
                        enum bufferevent_options options);

這裡的fd可以不指定,此時fd的引數是-1。如果指定了fd,這個fd必須是已經nonblock的。

int bufferevent_socket_connect (struct bufferevent *bev,
                                struct sockaddr    *address,
                                int                 addrlen);

這是對connect()的封裝。如果bev的fd是-1,那麼會自動呼叫socket(),並且設定nonblock。,隨後再非同步呼叫connect();如果fd已經指定了,那麼只是告訴bev去做connect()操作。
  正常情況下,這會引起BEV_EVENT_CONNECTED回撥

int bufferevent_socket_connect_hostname (
                struct bufferevent *bev,
                struct event_base  *dns_base,
                int                 family,
                const char         *hostname,
                int                 port);

這是connect()封裝的另一個版本,但是目標改為hostname。這會導致bufferevent自動去解析DNS。其中family可選以下值:AF_INET, AF_INET6, AF_UNSPEC
  dns_base引數可選。如果是NULL,那麼bufferevent會一直阻塞直到DNS解析完成——當然不推薦這麼做。如果帶了引數,則libevent會非同步處理DNS請求。
  剩下的工作與上面的connect封裝相同。

int bufferevent_socket_get_dns_error (struct bufferevent *bev);

通用的bufferevent操作

void bufferevent_free (struct bufferevent *bev);

釋放bfferevent。如果callback是defered的,那麼bufferevent會等到callback返回之後才釋放。
如果指定了BEV_OPT_CLOSE_ON_FREE,那麼socket也會被close掉。

typedef void (*bufferevent_data_cb) (struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb) (struct bufferevent *bev, short events, void *ctx);
void buffevent_setcb (struct buffevent    *bufev,
                      bufferevent_data_cb  readcb,
                      bufferevent_data_cb  writecb,
                      bufferevent_event_cb eventcb,
                      void                *cbarg);
void bufferevent_get_cb (struct buffevent     *bufev,
                         bufferevent_data_cb  *readcb_ptr,
                         bufferevent_data_cb  *writecb_ptr,
                         bufferevent_event_cb *eventcb_ptr,
                         void                **cbarg_ptr);

設定。獲取bufferevent的callback。如果不想使用某個callback,則傳入NULL。

void bufferevent_enable (struct bufferevent *bufev, short events);
void bufferevent_disable (struct bufferevent *bufev, short events);
short bufferevent_getenabled (struct bufferevent *bufev);

使能/禁用指定的的callback。預設情況下,剛初始化的bufferevent,write使能,而read禁止。

void bufferevent_setwatermark (struct buffevent *bev,
                               short             events,
                               size_t            lowmark,
                               size_t            highmark);

設定watermark。對於high-watermark,0表示無限。

struct evbuffer *bufferevent_get_input (struct bufferevent *bev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bev);

獲取到bufferevent中對應的read/write buffer。

int bufferevent_write (struct bufferevent *ev, const void *data, size_t size);
int bufferevent_write_buffer (struct bufferevent *bev, struct evbuffer *buf);

函式一:直接向bufferevent附加資料
函式二:將evbuffer的全部內容附加到bufferevent中並晴空evbuffer

size_t bufferevent_read (struct bufferevent *bev, void *data, size_t size);
int bufferevent_read_buffer (struct bufferevent *bev, struct evbuffer *buf);

函式一:直接從bufferevent中讀出資料,返回資料長度
函式二:將bufferevent中的全部資料抽取到evbuffer中

void bufferevent_set_timeouts (struct bufferevent  *bev,
                               const struct timeval *timeout_read,
                               const struct timeval *timeout_write);

設定timeout,使得當一段時間沒有資料時,觸發回撥函式。此時的實踐中會包含 BEV_EVENT_TIMEOUT

int bufferevent_flush (struct bufferevent *bufev,
                       short               iotype,
                       enum bufferevent_flush_mode state);

強制讀/寫儘可能多的資料。這個函式目前對socket沒有作用。

型別特定的bufferevent函式

以下幾個函式的含義正如字面意思,就不特別說明了

int bufferevent_priority_set (struct bufferevent *bev, int pri);
int bufferevent_get_priority (struct bufferevent *bev);

int bufferevent_setfd (struct bufferevent *bev, evutil_socket_t fd);
evutil_socket_t bufferevent_getfd (struct bufferevent *bev);

struct event_base *bufferevent_get_base (struct bufferevent *bev);
struct bufferevent *bufferevent_get_underlying (struct bufferevent *bev);

void bufferevent_lock   (struct bufferevent *bev);
void buyfferevent_unlock(struct bufferevent *bev);

Bufferevents:高階主題

這裡講了很多bufferevent的高階功能。本文章只是列出其中會使用到的部分

限制每次read/write的長度

int bufferevent_set_max_single_read (struct bufferevent *bev, size_t size);
int bufferevent_set_max_single_write(struct bufferevent *bev, size_t size);

同時也有相對應的get函式

速率(頻寬)限制

#define EV_RATE_LIMIT_MAX    EV_SSIZE_MAX
struct ev_token_bucket_cfg;
struct ev_token_bucket_cfg *ev_token_bucket_cfg_new (
                                size_t read_rate,     size_t read_burst,
                                size_t write_rate,    size_t write_burst,
                                const struct timeval *tick_len);
void ev_token_bucket_cfg_free (struct ev_token_bucket_cfg *cfg);
int bufferevent_set_rate_limit (struct bufferevent         *bev,
                                struct ev_token_bucket_cfg *cfg);

其中ev_token_bucket_cfg_new()的前四個函式的單位均為bytes/tick,而tick的單位由tick_len指定。如果tick_len為NULL,那麼預設為1秒。

Bufferevents 和 SSL

這裡其實是一個很重要的內容,講的是如何在bufferevent中使用SSL。Libevent將SSL深度耦合了進來,使得你可以很方便地使用bufferevent來完成SSL通訊。
  呃,缺點就是libevent和OpenSSL的缺點的集合。其實對於嵌入式開發來說,因為CPU是分立的,所以效能上的缺點並不明顯,最大的問題是佔用磁碟空間啊!Libevent的庫本身就不小,加上OpenSSL更是超大。我弄懂libevent的時候,我們的系統已經準備改用其他的非同步I/O和SSL庫,所以我也就不看了
  另外吐槽一下:我們這麼多年了還是沒時間把libevent和OpenSSL完全替換的工作做完,在這期間我自己都把libev、libuv、PolarSSL(mbedTLS)、cyaSSL看了……

系列篇

Libevent官方文件學習筆記(1. libevent_core部分)
Libevent官方文件學習筆記(2. bufferevent部分)(本文)
Libevent官方文件學習筆記(3. evbuffer部分)

相關文章