libevent學習筆記【使用篇】——6. Bufferevents基本概念

翻譯自:http://www.wangafu.NET/~nickm/libevent-book/Ref6_bufferevent.html
原文:http://blog.csdn.net/windeal3203/article/details/52815520

大多數時候,應用程序除了響應請求外,還需要處理數據(及其緩存)。當我們想要寫數據是,通常會有以下步驟

  • 決定要向連接中寫入什麼數據, 把這些數據放入緩存
  • 等待連接可寫
  • 寫入儘可能多的數據
  • 記住寫入了多少數據,如果還有數據沒寫完。等待連接再次變爲可寫狀態。

      這樣的I/O緩衝方式很常見,因而libevent爲此提供了一種通用機制。
      “bufferevent”由一個底層傳輸系統(比如socket),一個讀緩衝區和一個寫緩衝區組成。
      對於普通的events, 當底層傳輸系統可讀或者可寫時,調用回調方式; 而bufferevent提供了一種替代方式:它在已經寫入、或者讀出數據的時候才調用回調函數。
      libevent有多種bufferevent, 它們共享通用的接口。截止至本文撰寫時,已經存在以下類型:

  • 基於socket的bufferevent:
    在底層流式socket上發送和接收數據,使用event_*接口作爲其後端。

  • 異步IO的bufferevent:
    使用Windows IOCP接口來想底層流式socket發送和接收數據。(Windows only, 實驗性的)
  • 過濾型的bufferevent:
    在數據傳送到底層bufferevent對象之前,對到來和外出的數據進行前期處理的bufferevent,比如對數據進行壓縮或者轉換
  • 成對的bufferevent:

      注意:截止Libevent2.0.2-alpha版本,bufferevent接口還沒有完全覆蓋所有的bufferevent類型。換句話說,並不是下面介紹的每一個接口都能用於所有的bufferevent類型。Libevent開發者會在未來的版本中解決該問題。
      還要注意:bufferevent目前僅能工作在流式協議上,比如TCP。未來可能會支持數據報協議,比如UDP。
    本文所有的函數和類型都是在<event2/bufferevent.h>文件中聲明。與evbuffers相關的函數在<event2/buffer.h>中聲明,有關信息參考下一章。

1. bufferevent和evbuffers

  每一個bufferevent 都有一個輸入緩衝區和輸出緩衝區, 這些緩衝區(buffer)都是struct evbuffer 類型。 當你有數據要寫入bufferevent, 你要先把數據填入output buffer,; 當bufferevent上有數據需要讀取時,則可以從input buffer中抽取出來。
  evbuffer 接口支持很多操作,會在以後的章節中進行討論。

2. 回調函數和水位數

每一個bufferevent都有兩個數據相關的回到函數, 一個 讀回調和一個寫回調。 默認情況下,當有數據從底層傳輸讀取時,讀回調函數就會被調用; 當ouput buffer想底層傳輸寫入足夠多的數據時, 寫回調函數就會被調用。
  通過調整bufferevent的讀取和寫入“水位線”(watermarks),可以改變這些函數的默認行爲。
  每個bufferevent都有4個水位線:

  • 讀 低水位:
    當bufferevent的輸入緩衝區的數據量到達該水位線或者更高時,bufferevent的讀回調函數就會被調用。該水位線默認爲0,所以每一次讀取操作都會導致讀回調函數被調用。
  • 讀 高水位:
    如果bufferevent的輸入緩衝區的數據量到達該水位線時,那麼bufferevent就會停止讀取,直到輸入緩衝區中足夠多的數據被抽走,從而數據量再次低於該水位線。默認情況下該水位線是無限制的,所以從來不會因爲輸入緩衝區的大小而停止讀取操作。
  • 寫 低水位:
    當寫操作使得輸出緩衝區的數據量達到或者低於該水位線時,才調用寫回調函數。默認情況下,該值爲0,所以輸出緩衝區被清空時才調用寫回調函數。
  • 寫 高水位:
    並非由bufferevent直接使用,對於bufferevent作爲其他bufferevent底層傳輸系統的時候,該水位線纔有特殊意義。所以可以參考後面的過濾型bufferevent。
      bufferevent還提供了error 或者event 的回調函數,用來通知應用程序關於非數據相關的事件。比如:關閉連接或者發生錯誤。 爲此,定義了以下 event標誌:

  • BEV_EVENT_READING:讀操作期間發生了事件。具體哪個事件參見其他標誌。

  • BEV_EVENT_WRITING:寫操作期間發生了事件。具體哪個事件參見其他標誌。
  • BEV_EVENT_ERROR:在bufferevent操作期間發生了錯誤,可以調用EVUTIL_SOCKET_ERROR函數來得到更多的錯誤信息。
  • BEV_EVENT_TIMEOUT: bufferevent上發生了超時
  • BEV_EVENT_EOF: bufferevent上遇到了EOF標誌
  • BEV_EVENT_CONNECTED:在bufferevent上請求的連接已經完成

3. 延期回調

默認情況下, bufferevent的回調函數在響應的條件滿足時會立即執行(evbuffer 回調函數也是如此, 後面會講到)。 當依賴關係比較複雜時, 這種立即執行 的機制會導致一些問題。比如說, 假設 有一個回調函數 是用於在evbuffer A爲空時向其填入數據, 而另一個回調函數則在evbuffer A爲滿時 從中取出數據進行處理。如果所有這些調用都發生在棧上的話,在依賴關係足夠複雜的時候,有棧溢出的風險。
  爲了解決這個問題,你可以告訴bufferevent(或evbuffer)它的回調函數應該被延遲執行。當延遲迴調函數 的對應條件滿足時, 延遲迴調函數不會立即執行,而是加入到event_loop()調用隊列中。然後在常規的event回調之後執行。 。

4. bufferevent 的選項標誌

在穿件bufferevent時,可以通過一些選項來修改它的行爲:

  • BEV_OPT_CLOSE_ON_FREE: 當釋放bufferevent時,關閉底層的傳輸系統。 這將關閉底層套接字,釋放底層bufferevent等。
  • BEV_OPT_THREADSAFE: 自動爲bufferevent分配鎖, 從而在多線程中可以安全使用。
  • BEV_OPT_DEFER_CALLBACKS: bufferevent會將其所有回調函數進行延遲調用設置。
  • BEV_OPT_UNLOCK_CALLBACKS: 默認情況下, 當設置bufferevent爲線程安全的時候,任何用戶提供的回調函數調用時都會鎖住bufferevent的鎖, 設置改標誌可以在提供的回調函數被調用時不鎖住bufferevent的鎖。

5. 基於socket 的bufferevent

最簡單的bufferevents就是基於socket類型的bufferevent。基於socket的bufferevent使用Libevent底層event機制探測底層網絡socket何時準備好讀和寫,而且使用底層網絡調用(比如readvwritevWSASendWSARecv)進行傳送和接受數據。

5.1 創建 基於socket 的bufferevent

使用bufferevent_socket_new() 可以創建一個基於socket的bufferevent。

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

base:表示event_base
options:bufferevent選項的位掩(BEV_OPT_CLOSE_ON_FREE等)。
fd:參數是一個可選的socket文件描述符。如果希望以後再設置socket文件描述符,可以將fd置爲-1。

  Tip:要確保提供給bufferevent_socket_new的socket是非阻塞模式。Libevent提供了便於使用的evutil_make_socket_nonblocking來設置非阻塞模式。
  bufferevent_socket_new成功時返回一個bufferevent,失敗時返回NULL

5.2 在基於socket的bufferevent上發送連接

如果bufferevent上的socket還沒有建立連接, 我們可以發送一個新的連接:

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

address和addrlen參數類似於標準的connect函數。如果該bufferevent尚未設置socket,則調用該函數爲該bufferevent會分配一個新的流類型的socket,並且置其爲非阻塞的。
  如果bufferevent已經設置了一個socket,則調用數bufferevent_socket_connect會告知Libevent該socket尚未建立連接,在建立連接成功之前,不應該在其上進行讀寫操作。
  在建立連接成功之前,向輸出緩衝區添加數據是可以的。
  該函數如果在建鏈成功時,返回0,如果發生錯誤,則返回-1.
Example

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

    bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);

    bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);

    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_base_connect()函數是在Libevent-2.0.2-alpha版本引入的,在這之前,需要手動調用connect()函數,並且當連接建立的時候,bufferevent會將其作爲寫事件進行報告。
注意:如果使用bufferevent_socket_connect進行建鏈的話,會得到BEV_EVENT_CONNECTED事件。如果自己手動調用connect(),則會得到write事件。
如果在手動調用connect()的情況下,仍然想在建鏈成功的時候得到BEV_EVENT_CONNECTED事件,可以在connect()返回-1,並且errnoEAGAINEINPROGRESS之後,調用bufferevent_socket_connect(bev, NULL, 0)函數。

5.3 通過hostname發射連接

經常性的,你可能希望將解析主機名和建立連接操作合成一個單獨的操作,這時可以使用下面的接口:

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

該函數解析DNS名字hostname,查找family類型的地址(family的類型可以是AF_INET, AF_INET6AF_UNSPEC)。如果解析主機名失敗,會以error event調用回調函數。如果成功了,則會像 bufferevent_connect一樣,接着進行建立連接。
dns_base參數是可選的。如果該參數爲空,則Libevent會一直阻塞,等待主機名解析完成,一般情況下不會這麼做。如果提供了該參數,則Libevent使用它進行異步的主機名解析。參考Chapter 9更多內容。
類似於bufferevent_socket_connect,該函數會告知Libevent,bufferevent上已存在的socket尚未建立連接,在解析完成,並且連接建立成功之前,不應該在其上進行讀寫操作。
如果發生了錯誤,有可能是DNS解析錯誤。可以通過調用函數bufferevent_socket_get_dns_error函數得到最近發生的錯誤信息。如果該函數返回的錯誤碼爲0,則表明沒有檢查到任何DNS錯誤。
Example: Trivial HTTP v0 client.

/* Don't actually copy this code: it is a poor way to implement an
   HTTP client.  Have a look at evhttp instead.
*/
#include <event2/dns.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/event.h>

#include <stdio.h>

void readcb(struct bufferevent *bev, void *ptr)
{
    char buf[1024];
    int n;
    struct evbuffer *input = bufferevent_get_input(bev);
    while ((n = evbuffer_remove(input, buf, sizeof(buf))) > 0) {
        fwrite(buf, 1, n, stdout);
    }
}

void eventcb(struct bufferevent *bev, short events, void *ptr)
{
    if (events & BEV_EVENT_CONNECTED) {
         printf("Connect okay.\n");
    } else if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) {
         struct event_base *base = ptr;
         if (events & BEV_EVENT_ERROR) {
                 int err = bufferevent_socket_get_dns_error(bev);
                 if (err)
                         printf("DNS error: %s\n", evutil_gai_strerror(err));
         }
         printf("Closing\n");
         bufferevent_free(bev);
         event_base_loopexit(base, NULL);
    }
}

int main(int argc, char **argv)
{
    struct event_base *base;
    struct evdns_base *dns_base;
    struct bufferevent *bev;

    if (argc != 3) {
        printf("Trivial HTTP 0.x client\n"
               "Syntax: %s [hostname] [resource]\n"
               "Example: %s www.google.com /\n",argv[0],argv[0]);
        return 1;
    }

    base = event_base_new();
    dns_base = evdns_base_new(base, 1);

    bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(bev, readcb, NULL, eventcb, base);
    bufferevent_enable(bev, EV_READ|EV_WRITE);
    evbuffer_add_printf(bufferevent_get_output(bev), "GET %s\r\n", argv[2]);
    bufferevent_socket_connect_hostname(
        bev, dns_base, AF_UNSPEC, argv[1], 80);
    event_base_dispatch(base);
    return 0;
}

6. 通用的bufferevent操作

本節介紹的bufferevent能夠在不同的bufferevent實現上工作

6.1 釋放bufferevent

void  bufferevent_free(struct  bufferevent *bev);

該函數釋放buffereventbufferevent在內部具有引用計數,所以即使當釋放bufferevent時,如果bufferevent還有未決的延遲迴調,那該bufferevent在該回調完成之前也不會刪除。
bufferevent_free函數會盡快釋放bufferevent。然而,如果bufferevent的輸出緩衝區中尚有殘留數據要寫,該函數也不會在釋放bufferevent之前對緩衝區進行flush
如果設置了BEV_OPT_CLOSE_ON_FREE標誌,並且該buffereventsocket或者使用了其他底層bufferevent作爲其傳輸系統,則在釋放該bufferevent時,會關閉對應的傳輸系統。
這些函數在Libevent 0.8.中引入。

6.2 回調函數、水位線、使能操作

typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev,
    short events, void *ctx);

void bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg);

void bufferevent_getcb(struct bufferevent *bufev,
    bufferevent_data_cb *readcb_ptr,
    bufferevent_data_cb *writecb_ptr,
    bufferevent_event_cb *eventcb_ptr,
    void **cbarg_ptr);

  函數bufferevent_setcb() 可以改變bufferevent的回調函數。readcbwritecbeventcb函數 分別在 有足夠數據可讀、有足夠數據可寫、有event時間發生時被調用。 這些回調函數的第一個參數就是發生了events的bufferevent; 而最後一個參數則是由用戶提供的bufferevent_setcbcbarg參數;用戶可以通過event回調函數的events參數是event標誌的位掩碼:參考上面的“回調函數和水位線”一節
  我們可以通過向bufferevent_setcb() 傳遞NULL參數來禁用一些回調函數。需要注意的是,cbarg 參數是針對所有回調函數的。
  我們可以通過向bufferevent_getcb 傳遞指針來獲取當前的回調函數。該函數會將*readcb_ptr設置爲當前的讀回調函數,*writecb_ptr設置爲寫回調函數,*eventcb_ptr設置爲當前的event回調函數,並且*cbarg_ptr設置爲當前回調函數的參數。對於被設置爲NULL 的指針,則會忽略它。
  函數bufferevent_setcb() 在Libevent 1.4.4中引入. bufferevent_data_cbbufferevent_event_cb 則是在 Libevent 2.0.2-alpha中新引入的. The bufferevent_getcb() 則是在 2.1.1-alpha 中新加入的。

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

short bufferevent_get_enabled(struct bufferevent *bufev);

我們可以在bufferevent上enable或者disable事件 EV_READ, EV_WRITE, or EV_READ|EV_WRITE 。 當disable了讀寫操作,則bufferevent不會讀取或寫入數據。
當output buffer 爲空時,沒必要disable寫動作,bufferevent會自動禁止掉寫動作。
類似的,當輸入緩衝區達到它的高水位線的時候,沒必要禁止讀操作:bufferevent會自動停止讀操作,而且在有空間讀取的時候,又重新開啓讀操作。
默認情況下,新創建的bufferevent會enable寫操作,而禁止讀操作。
可以通過bufferevent_get_enabled() 來獲取當前bufferevent中有那些events被enable了。

bufferevent_get_enabled() 是在 2.0.3-alpha中引入的, 而其它函數在Libevent 0.8就被引進了。

void bufferevent_setwatermark(struct bufferevent *bufev, short events,
    size_t lowmark, size_t highmark);

bufferevent_setwatermark調整一個bufferevent的讀水位線,或寫水位線,或兩者一起調整。如果在events參數中設置了EV_READ參數,則會調整讀水位線,如果設置了EV_WRITE標誌,則會調整寫水位線。將高水位線標誌置爲0,表示“無限制”。
該函數在Libevent 1.4.4.中引入 。

Example

#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>

#include <stdlib.h>
#include <errno.h>
#include <string.h>

struct info {
    const char *name;
    size_t total_drained;
};

void read_callback(struct bufferevent *bev, void *ctx)
{
    struct info *inf = ctx;
    struct evbuffer *input = bufferevent_get_input(bev);
    size_t len = evbuffer_get_length(input);
    if (len) {
        inf->total_drained += len;
        evbuffer_drain(input, len);
        printf("Drained %lu bytes from %s\n",
             (unsigned long) len, inf->name);
    }
}

void event_callback(struct bufferevent *bev, short events, void *ctx)
{
    struct info *inf = ctx;
    struct evbuffer *input = bufferevent_get_input(bev);
    int finished = 0;

    if (events & BEV_EVENT_EOF) {
        size_t len = evbuffer_get_length(input);
        printf("Got a close from %s.  We drained %lu bytes from it, "
            "and have %lu left.\n", inf->name,
            (unsigned long)inf->total_drained, (unsigned long)len);
        finished = 1;
    }
    if (events & BEV_EVENT_ERROR) {
        printf("Got an error from %s: %s\n",
            inf->name, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
        finished = 1;
    }
    if (finished) {
        free(ctx);
        bufferevent_free(bev);
    }
}

struct bufferevent *setup_bufferevent(void)
{
    struct bufferevent *b1 = NULL;
    struct info *info1;

    info1 = malloc(sizeof(struct info));
    info1->name = "buffer 1";
    info1->total_drained = 0;

    /* ... Here we should set up the bufferevent and make sure it gets
       connected... */

    /* Trigger the read callback only whenever there is at least 128 bytes
       of data in the buffer. */
    bufferevent_setwatermark(b1, EV_READ, 128, 0);

    bufferevent_setcb(b1, read_callback, NULL, event_callback, info1);

    bufferevent_enable(b1, EV_READ); /* Start reading. */
    return b1;
}

7. bufferevent中的數據操作

如果不能操作讀寫的數據,則從網絡中讀寫數據沒有任何意義。bufferevent提供函數可以操作讀寫的數據。

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

這兩個函數功能非常強大,它們分別反悔bufferevent的input bufferoutput buffer. 在evbuffer類型上所能進行的所有操作,可以參考下一章。
注意,應用程序只能從input buffer中移走(而不是添加)數據,而且只能向output buffer添加(而不是移走)數據。
如果bufferevent上的寫操作因爲數據太少而停滯(或者讀操作因爲數據太多而停滯),則向output buffer中添加數據(或者從input buffer中移走數據)可以自動重啓寫(讀)操作。

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

這些函數向bufferevent的output buffer中添加數據。調用bufferevent_write函數添加data中的size個字節的數據到輸出緩衝區的末尾。調用 bufferevent_write_buffer函數則將buf中所有數據都移動到output buffer的末尾。這些函數返回0表示成功,返回-1表示發生了錯誤。
這兩個函數在Libevent 0.8就引入了。

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

這兩個函數從bufferevent的input buffer中移走數據。bufferevent_read函數從input buffer中移動size個字節到data中。它返回實際移動的字節數。bufferevent_read_buffer函數則移動輸入緩衝區中的所有數據到buf中,該函數返回0表示成功,返回-1表示失敗。
注意bufferevent_read函數中,data緩衝區必須有足夠的空間保存size個字節。

bufferevent_read() 在 libevent 0.8中引入,bufferevent_read_buffer 在Libevent 2.0.1-alpha. 中才引入。

* Example*

#include <event2/bufferevent.h>
#include <event2/buffer.h>

#include <ctype.h>

void
read_callback_uppercase(struct bufferevent *bev, void *ctx)
{
        /* This callback removes the data from bev's input buffer 128
           bytes at a time, uppercases it, and starts sending it
           back.

           (Watch out!  In practice, you shouldn't use toupper to implement
           a network protocol, unless you know for a fact that the current
           locale is the one you want to be using.)
         */

        char tmp[128];
        size_t n;
        int i;
        while (1) {
                n = bufferevent_read(bev, tmp, sizeof(tmp));
                if (n <= 0)
                        break; /* No more data. */
                for (i=0; i<n; ++i)
                        tmp[i] = toupper(tmp[i]);
                bufferevent_write(bev, tmp, n);
        }
}

struct proxy_info {
        struct bufferevent *other_bev;
};
void
read_callback_proxy(struct bufferevent *bev, void *ctx)
{
        /* You might use a function like this if you're implementing
           a simple proxy: it will take data from one connection (on
           bev), and write it to another, copying as little as
           possible. */
        struct proxy_info *inf = ctx;

        bufferevent_read_buffer(bev,
            bufferevent_get_output(inf->other_bev));
}

struct count {
        unsigned long last_fib[2];
};

void
write_callback_fibonacci(struct bufferevent *bev, void *ctx)
{
        /* Here's a callback that adds some Fibonacci numbers to the
           output buffer of bev.  It stops once we have added 1k of
           data; once this data is drained, we'll add more. */
        struct count *c = ctx;

        struct evbuffer *tmp = evbuffer_new();
        while (evbuffer_get_length(tmp) < 1024) {
                 unsigned long next = c->last_fib[0] + c->last_fib[1];
                 c->last_fib[0] = c->last_fib[1];
                 c->last_fib[1] = next;

                 evbuffer_add_printf(tmp, "%lu", next);
        }

        /* Now we add the whole contents of tmp to bev. */
        bufferevent_write_buffer(bev, tmp);

        /* We don't need tmp any longer. */
        evbuffer_free(tmp);
}

8. 讀寫 超時

同其他events一樣,可以設置timeout時間,當bufferevent在timeout時間消逝後還沒有成功的讀或寫任何數據,則可以觸發某個超時事件。

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

timeout設置爲NULL,意味着移除超時時間;然而在Libevent 2.1.2-alpha版本之前,這種方式並非在所有event類型上都有效。(對於較老版本的,取消超時時間的有效方法是,可以將超時時間設置爲好幾天,並且/或者使eventcb函數忽略BEV_TIMEOUT事件)。
當bufferevent試圖讀取數據時,等待了timeout_read秒還沒有數據,則讀超時事件就會觸發。當bufferevent試圖寫數據時,至少等待了timeout_write秒,則寫超時事件就會觸發。
注意,只有在bufferevent讀或寫的時候,纔會對超時時間進行計時。換句話說,如果bufferevent上禁止了讀操作,或者當輸入緩衝區滿(達到高水位線)時,則讀超時時間不會使能。類似的,如果寫操作未被使能,或者沒有數據可寫,則寫超時時間也會被禁止。

當讀或寫超時發生的時候,則bufferevent上相應的讀寫操作就會被禁止。相應的event回調函數就會以BEV_EVENT_TIMEOUT|BEV_EVENT_READINGBEV_EVENT_TIMEOUT|BEV_EVENT_WRITING進行調用。

9. 在bufferevent上進行flush

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

對一個bufferevent進行flush,可以強制bufferevent儘可能多的從底層傳輸系統上讀取或者寫入數據,而忽略其他可能阻止寫入的限制條件。該函數的細節依賴於不同類型的bufferevent。
參數iotype 可以是EV_READ, EV_WRITE, EV_READ|EV_WRITE指明處理讀操作、寫操作,還是兩者都處理. 參數state可以是BEV_NORMAL, BEV_FLUSH,BEV_FINISHED 之一. BEV_FINISHED 指明應該告訴另一端已經沒有數據可以發送了;BEV_NORMALBEV_FLUSH 取決於bufferevent的類型。
bufferevent_flush函數返回-1表示失敗,返回0表示沒有任何數據被flush,返回1表示由數據被flush。
目前(Libevent2.0.5-beta),bufferevent_flush函數只在某些bufferevent類型上進行了實現,特別是基於socket的bufferevent並不支持該操作。

10. 特定類型的bufferevent函數

這些函數並非在所有bufferevent類型上都能使用

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

該函數將實現bufev的events的優先級調整爲pri,關於優先級更多的信息,可以參考event_priority_set函數。
返回0表示成功,返回-1表示失敗,該函數只能工作在基於socket的bufferevent上。

函數bufferevent_priority_set() 在 Libevent 1.0 中引入; bufferevent_get_priority() 直到 2.1.2-alpha.纔出現。

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

該函數設置或者返回一個基於fdevent的文件描述符。只有基於socketbufferevent支持setfd操作。這些函數返回-1表示失敗,setfd返回0表示成功。
函數bufferevent_setfd()Libevent 1.4.4 中引入。 bufferevent_getfd() 在 Libevent 2.0.2-alpha 中引入。

struct event_base *bufferevent_get_base(struct bufferevent *bev);

該函數返回buffereventevent_base. 該函數在2.0.9-rc中引入 .

struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev);

如果bufferevent作爲其他bufferevent的底層傳輸系統的話,則該函數返回該底層bufferevent。參考過濾型bufferevent,獲得關於這種情況的更多信息。
這個函數在Libevent 2.0.2-alpha.中引進。

11. 對bufferevent手動加鎖或者解鎖

類似於evbuffers,有時希望保證在bufferevent上的一系列操作是原子性的。Libevent提供了可以手動加鎖和解鎖bufferevent的函數。

void bufferevent_lock(struct bufferevent *bufev);
void bufferevent_unlock(struct bufferevent *bufev);

注意,如果一個bufferevent在創建時沒有指定BEV_OPT_THREADSAFE 標誌,或者Libevent的線程支持功能沒有激活,則加鎖一個bufferevent沒有作用。
通過該函數對bufferevent進行加鎖的同時,也會加鎖evbuffers。這些函數都是遞歸的:對一個已經加鎖的bufferevent再次加鎖是安全的。當然,對於每次鎖定都必須進行一次解鎖。

12. 過時的bufferevent函數

在Libevent1.4和Libevent2.0之間,bufferevent的後臺代碼經歷了大量的修改。在老接口中,訪問·bufferevent·結構的內部是很正常的,而且,還會經常使用依賴於這種訪問方式的宏。
讓問題變得更加複雜的是,老的代碼中,有時會使用以evbuffer爲前綴的bufferevent函數。
下表是一個Libevent2.0之前版本的函數概述

Current name Old name
bufferevent_data_cb evbuffercb
bufferevent_event_cb everrorcb
BEV_EVENT_READING EVBUFFER_READ
BEV_EVENT_WRITE EVBUFFER_WRITE
BEV_EVENT_EOF EVBUFFER_EOF
BEV_EVENT_ERROR EVBUFFER_ERROR
BEV_EVENT_TIMEOUT EVBUFFER_TIMEOUT
bufferevent_get_input(b) EVBUFFER_INPUT(b)
bufferevent_get_output(b) EVBUFFER_OUTPUT(b)

老版本的函數定義在event.h中,而不是event2/bufferevent.h中。
如果仍然需要訪問bufferevent內部結構中的普通部分,可以包含event2/bufferevent_struct.h。我們不建議這樣做:bufferevent結構的內容在不同的Libevent版本中經常會發生改變。如果包含了event2/bufferevent_compat.h文件,可以使用本節介紹的宏和名字。
老版本的代碼中,創建bufferevent結構的接口有所不同:

struct bufferevent  *bufferevent_new(evutil_socket_t  fd,
    evbuffercb  readcb,  evbuffercb  writecb,  everrorcb  errorcb,  void  *cbarg);
int  bufferevent_base_set(struct  event_base  *base,  struct bufferevent  *bufev);

bufferevent_new()函數僅能創建基於socketbufferevent,而且還是在不推薦使用的“當前”event_base上。可以通過調用bufferevent_base_set來調整一個socket buffereventevent_base

老版本的代碼設置超時的秒數,而不是設置結構體timeval

void  bufferevent_settimeout(struct  bufferevent  *bufev,
                    int  timeout_read, int  timeout_write);

最後注意,在Libevent2.0之前的版本中,底層的evbuffer實現是非常低效的,因此使用bufferevent構建高性能的程序是不太可能的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章