Libevent應用 (三) 資料緩衝

嚇人的猿發表於2018-03-03

3 資料緩衝

​ 很多時候,除了響應事件之外,應用還希望做一定的資料緩衝。比如說,寫入資料的時候,通常的執行模式是:

​ (1)決定要向連線寫入一些資料,把資料放入到緩衝區中

​ (2)等待連線可以寫入

​ (3)寫入儘量多的資料

​ (4)記住寫入了多少資料,如果還有更多資料要寫入,等待連線再次可以寫入

​ 這種緩衝IO模式很通用,libevent為此提供了一種通用機制,即bufferevent。bufferevent由一個底層的傳輸埠(如套接字),一個讀取緩衝區和一個寫入緩衝區組成。與通常的事件在底層傳輸埠已經就緒,可以讀取或者寫入的時候執行回撥不同的是,bufferevent在讀取或者寫入了足夠量的資料之後呼叫使用者提供的回撥。

3.1 bufferevent和evbuffer

​ 每個bufferevent都有一個輸入緩衝區和一個輸出緩衝區,它們的型別都是“struct evbuffer”。有資料要寫入到bufferevent時,新增資料到輸出緩衝區;bufferevent中有資料供讀取的時候,從輸入緩衝區抽取(drain)資料。

3.2 回撥水位

​ 每個bufferevent有兩個資料相關的回撥:一個讀取回撥和一個寫入回撥。預設情況下,從底層傳輸埠讀取了任意量的資料之後會呼叫讀取回撥;輸出緩衝區中足夠量的資料被清空到底層傳輸埠後寫入回撥會被呼叫。通過調整bufferevent的讀取和寫入“水位(watermarks)”可以覆蓋這些函式的預設行為。

​ 每個bufferevent有四個水位:

​ (1)讀取低水位:讀取操作使得輸入緩衝區的資料量在此級別或者更高時,讀取回撥將被呼叫。預設值為0,所以每個讀取操作都會導致讀取回撥被呼叫。

​ (2)讀取高水位:輸入緩衝區中的資料量達到此級別後,bufferevent將停止讀取,直到輸入緩衝區中足夠量的資料被抽取,使得資料量低於此級別。預設值是無限,所以永遠不會因為輸入緩衝區的大小而停止讀取。

​ (3)寫入低水位:寫入操作使得輸出緩衝區的資料量達到或者低於此級別時,寫入回撥將被呼叫。預設值是0,所以只有輸出緩衝區空的時候才會呼叫寫入回撥。

​ (4)寫入高水位:bufferevent沒有直接使用這個水位。它在bufferevent用作另外一個bufferevent的底層傳輸埠時有特殊意義。

​ bufferevent也有“錯誤”或者“事件”回撥,用於嚮應用通知非面向資料的事件,如連線已經關閉或者發生錯誤。定義了下列事件標誌:

BEV_EVENT_READING:#讀取操作時發生某事件,具體是哪種事件請看其他標誌。
BEV_EVENT_WRITING:#寫入操作時發生某事件,具體是哪種事件請看其他標誌。
BEV_EVENT_ERROR:#操作時發生錯誤。關於錯誤的更多資訊,請呼叫EVUTIL_SOCKET_ERROR(),獲取錯誤號。
BEV_EVENT_TIMEOUT:#發生超時。
BEV_EVENT_EOF:#遇到檔案結束指示。
BEV_EVENT_CONNECTED:#請求的連線過程已經完成。

3.3 延遲迴調

​ 預設情況下,bufferevent的回撥在相應的條件發生時立即被執行。(evbuffer的回撥也是這樣的)在依賴關係複雜的情況下,這種立即呼叫會製造麻煩。比如說,假如某個回撥在evbuffer A空的時候向其中移入資料,而另一個回撥在evbuffer A滿的時候從中取出資料。這些呼叫都是在棧上發生的,在依賴關係足夠複雜的時候,有棧溢位的風險。

​ 要解決此問題,可以請求bufferevent(或者evbuffer)延遲其回撥。條件滿足時,延遲迴調不會立即呼叫,而是在event_loop()呼叫中被排隊,然後在通常的事件回撥之後執行。

3.4 bufferevent的選項標誌

建立bufferevent時可以使用一個或者多個標誌修改其行為。可識別的標誌有:

BEV_OPT_CLOSE_ON_FREE:#釋放bufferevent時關閉底層傳輸埠。這將關閉底層套接字,釋放底層bufferevent等。
BEV_OPT_THREADSAFE:#自動為bufferevent分配鎖,這樣就可以安全地在多個執行緒中使用bufferevent。
BEV_OPT_DEFER_CALLBACKS:#設定這個標誌時,bufferevent延遲所有回撥,如上所述。

#預設情況下,如果設定bufferevent為執行緒安全的,則bufferevent會在呼叫使用者提供的回撥時進行鎖定。
#設定這個選項會讓libevent在執行回撥的時候不進行鎖定。
BEV_OPT_UNLOCK_CALLBACKS:

3.5 與基於套接字的bufferevent一起工作

​ 基於套接字的bufferevent是最簡單的,它使用libevent的底層事件機制來檢測底層網路套接字是否已經就緒,可以進行讀寫操作,並且使用底層網路呼叫(如readv、writev、WSASend、WSARecv)來傳送和接收資料。

3.5.1 建立基於套接字的bufferevent

​ 可以使用bufferevent_socket_new()建立基於套接字的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是一個可選的表示套接字的檔案描述符。如果想以後設定檔案描述符,可以設定fd為-1。

​ 成功時函式返回一個bufferevent,失敗則返回NULL。

3.5.2 在基於套接字的bufferevent上啟動連線

​ 如果bufferevent的套接字還沒有連線上,可以啟動新的連線。

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

​ address和addrlen引數跟標準呼叫connect()的引數相同。如果還沒有為bufferevent設定套接字,呼叫函式將為其分配一個新的流套接字,並且設定為非阻塞的。

​ 如果已經為bufferevent設定套接字,呼叫bufferevent_socket_connect()將告知libevent套接字還未連線,直到連線成功之前不應該對其進行讀取或者寫入操作。

​ 連線完成之前可以向輸出緩衝區新增資料。

​ 如果連線成功啟動,函式返回0;如果發生錯誤則返回-1。

注意:如果使用bufferevent_socket_connect()發起連線,將只會收到BEV_EVENT_CONNECTED事件。如果自己呼叫connect(),則連線上將被報告為寫入事件。

3.6 通用bufferevent操作

3.6.1 釋放bufferevent

void bufferevent_free(struct bufferevent *bev);

​ 這個函式釋放bufferevent。bufferevent內部具有引用計數,所以,如果釋放bufferevent時還有未決的延遲迴調,則在回撥完成之前bufferevent不會被刪除。

​ 如果設定了BEV_OPT_CLOSE_ON_FREE標誌,並且bufferevent有一個套接字或者底層bufferevent作為其傳輸埠,則釋放bufferevent將關閉這個傳輸埠。

3.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的一個或者多個回撥。readcb、writecb和eventcb函式將分別在已經讀取足夠的資料、已經寫入足夠的資料,或者發生錯誤時被呼叫。每個回撥函式的第一個引數都是發生了事件的bufferevent,最後一個引數都是呼叫bufferevent_setcb()時使用者提供的cbarg引數:可以通過它向回撥傳遞資料。事件回撥的events引數是一個表示事件標誌的位掩碼:請看前面的“回撥和水位”節。

​ 要禁用回撥,傳遞NULL而不是回撥函式。

注意:bufferevent的所有回撥函式共享單個cbarg,所以修改它將影響所有回撥函式。

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

​ 可以啟用或者禁用bufferevent上的EV_READ、EV_WRITE或者EV_READ | EV_WRITE事件。沒有啟用讀取或者寫入事件時,bufferevent將不會試圖進行資料讀取或者寫入。

​ 沒有必要在輸出緩衝區空時禁用寫入事件:bufferevent將自動停止寫入,然後在有資料等待寫入時重新開始。

​ 類似地,沒有必要在輸入緩衝區高於高水位時禁用讀取事件:bufferevent將自動停止讀取,然後在有空間用於讀取時重新開始讀取。

​ 預設情況下,新建立的bufferevent的寫入是啟用的,但是讀取沒有啟用。

​ 可以呼叫bufferevent_get_enabled()確定bufferevent上當前啟用的事件。

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

​ bufferevent_setwatermark()函式調整單個bufferevent的讀取水位、寫入水位,或者同時調整二者。(如果events引數設定了EV_READ,調整讀取水位。如果events設定了EV_WRITE標誌,調整寫入水位)對於高水位,0表示“無限”。

3.6.3 操作bufferevent中的資料

  • 寫資料

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

​ 這個函式向bufferevent的輸出緩衝區新增資料。bufferevent_write()將記憶體中從data處開始的size位元組資料新增到輸出緩衝區的末尾。成功時這些函式都返回0,發生錯誤時則返回-1。

注意:即使沒有呼叫bufferevent_enable使能寫事件,呼叫bufferevent_write時,內部會新增寫事件的監控,觸發寫回撥函式後,再把寫事件清除掉,寫回撥函式返回時,這個時候檢查一下輸出緩衝區是否低於寫低水位,低了就呼叫一下寫bufferevent的回撥函式。

  • 讀資料

size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

​ 這個函式從bufferevent的輸入緩衝區移除資料。bufferevent_read()至多從輸入緩衝區移除size位元組的資料,將其儲存到記憶體中data處。

注意:對於bufferevent_read(),data處的記憶體塊必須有足夠的空間容納size位元組資料。

3.6.4 讀寫超時

​ 跟其他事件一樣,可以要求在一定量的時間已經流逝,而沒有成功寫入或者讀取資料的時候呼叫一個超時回撥。

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

​ 設定超時為NULL會移除超時回撥。

​ 試圖讀取資料的時候,如果至少等待了timeout_read秒,則讀取超時事件將被觸發。試圖寫入資料的時候,如果至少等待了timeout_write秒,則寫入超時事件將被觸發。

​ 注意,只有在讀取或者寫入的時候才會計算超時。也就是說,如果bufferevent的讀取被禁止,或者輸入緩衝區滿(達到其高水位),則讀取超時被禁止。類似的,如果寫入被禁止,或者沒有資料待寫入,則寫入超時被禁止。

​ 讀取或者寫入超時發生時,相應的讀取或者寫入操作被禁止,然後超時事件回撥被呼叫,帶有標誌BEV_EVENT_TIMEOUT | BEV_EVENT_READING或者BEV_EVENT_TIMEOUT | BEV_EVENT_WRITING。

3.7 型別特定的bufferevent函式

​ 這些bufferevent函式不能支援所有bufferevent型別。

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

​ 這個函式調整bufev的優先順序為pri。關於優先順序的更多資訊請看event_priority_set()。

成功時函式返回0,失敗時返回-1。這個函式僅能用於基於套接字的bufferevent。

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

​ 這些函式設定或者返回基於fd的事件的檔案描述符。只有基於套接字的bufferevent支援setfd()。兩個函式都在失敗時返回-1;setfd()成功時返回0。

struct event_base *bufferevent_get_base(struct bufferevent *bev);

​ 這個函式返回bufferevent的event_base。

3.8 手動鎖定和解鎖

​ 有時候需要確保對bufferevent的一些操作是原子地執行的。為此,libevent提供了手動鎖定和解鎖bufferevent的函式。

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

注意:如果建立bufferevent時沒有指定BEV_OPT_THREADSAFE標誌,或者沒有啟用libevent的執行緒支援,則鎖定操作是沒有效果的。

​ 用這個函式鎖定bufferevent將自動同時鎖定相關聯的evbuffer。這些函式是遞迴的:鎖定已經持有鎖的bufferevent是安全的。當然,對於每次鎖定都必須進行一次解鎖。

3.9 示例程式碼

伺服器tcp_server.c:

/*************************************************************************
# File Name: tcp_server.c
# Author: wenong
# mail: huangwenlong@520it.com
# Created Time: 2016年09月03日 星期六 21時51分08秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define SERVERPORT 8888
#define MAXBYTES 1024


void read_buf_cb(struct bufferevent* bev, void* cbarg)
{
    int ret, i;
    char buf[MAXBYTES];
    ret = bufferevent_read(bev, buf, sizeof(buf));
    printf("read_buf_cd length %d\n", ret);
    for(i = 0; i < ret; i++)
    {
        buf[i] = toupper(buf[i]);
    }
    bufferevent_write(bev, buf, ret);
    
}


void event_cb(struct bufferevent* bev, short event, void* cbarg)
{
    struct event_base* base = (struct event_base*)cbarg;
    if(BEV_EVENT_READING & event)
        puts("BEV_EVENT_READING");
    
    if(BEV_EVENT_WRITING & event)
        puts("BEV_EVENT_WRITING");

    if(BEV_EVENT_ERROR & event)
        puts("BEV_EVENT_ERROR");

    if(BEV_EVENT_EOF & event)
    {
        puts("BEV_EVENT_EOF");
        bufferevent_free(bev);
    }

}


void  accept_cb(evutil_socket_t serverfd, short what, void * arg)
{
    
    struct sockaddr_in clientaddr;
    struct event* ev;
    struct bufferevent* bev;
    struct event_base* base = (struct event_base*)arg;
    int clientaddrlen;
    int clientfd;
    puts("Accept client connect\n");
    clientaddrlen = sizeof(clientaddr);
    bzero((void*)&clientaddr, sizeof(clientaddr));
    clientfd  = accept(serverfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
    printf("recv clientfd %d\n", clientfd);
    evutil_make_socket_nonblocking(clientfd);
    bev = bufferevent_socket_new(base, clientfd, BEV_OPT_CLOSE_ON_FREE 
            | BEV_OPT_DEFER_CALLBACKS);
    bufferevent_setcb(bev, (bufferevent_data_cb)read_buf_cb
            , NULL, (bufferevent_event_cb)event_cb, (void*)base);
    bufferevent_enable(bev, EV_READ);
    bufferevent_setwatermark(bev, EV_READ, 10, 0);

}

void main_loop(evutil_socket_t fd)
{
    struct event_base * base;   
    struct event* ev;
    base = event_base_new();
    ev = event_new(base, fd, EV_READ | EV_PERSIST, (event_callback_fn)accept_cb, (void*)base);
    event_add(ev, NULL);
    puts("server begin listenning\n");
    event_base_dispatch(base);
    event_free(ev);
    event_base_free(base);
}


int main(int argc, char** argv)
{
    int serverfd;
    socklen_t serveraddrlen;
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;    
    serveraddr.sin_port = htons(SERVERPORT);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverfd = socket(AF_INET, SOCK_STREAM, 0);
    serveraddrlen = sizeof(serveraddr);
    bind(serverfd, (struct sockaddr*)&serveraddr, serveraddrlen);
    listen(serverfd, 128);
    main_loop(serverfd);
    close(serverfd);
    return 0;
}

客戶端tcp_client.c

/*************************************************************************
# File Name: tcp_client.c
# Author: wenong
# mail: huangwenlong@520it.com
# Created Time: 2016年09月03日 星期六 22時10分11秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define SERVERIP "127.0.0.1"
#define SERVERPORT 8888
#define MAXBYTES 1024

void* cmd_msg_cb(evutil_socket_t stdinfd, short what, void* arg)
{
    int ret;
    struct bufferevent* bev = (struct bufferevent*)arg;
    char buf[MAXBYTES];
    puts("get msg from stdin:");
    ret = read(stdinfd, buf, sizeof(buf));
    bufferevent_write(bev, buf, ret);
}


void read_buf_cb(struct bufferevent* bev, void* cbarg)
{
    int ret;
    char buf[MAXBYTES];
    ret = bufferevent_read(bev, buf, sizeof(buf));
    write(STDOUT_FILENO, buf, ret);
}

void event_cb(struct bufferevent* bev, short event, void* cbarg)
{
    struct event_base* base = (struct event_base*)cbarg;
    if(BEV_EVENT_READING & event)
        puts("BEV_EVENT_READING");
    
    if(BEV_EVENT_WRITING & event)
        puts("BEV_EVENT_WRITING");

    if(BEV_EVENT_ERROR & event)
        puts("BEV_EVENT_ERROR");

    if(BEV_EVENT_EOF & event)
    {
        puts("BEV_EVENT_EOF");
        event_base_loopexit(base, NULL);
        //event_base_loopexit(bufferevent_get_base(bev), NULL);
    }

}
void main_loop(int clientfd)
{
    struct event_base* base;
    struct bufferevent* bev;
    struct event*ev_stdin;  
    base = event_base_new();
    
    
    bev = bufferevent_socket_new(base, clientfd, BEV_OPT_CLOSE_ON_FREE 
            | BEV_OPT_DEFER_CALLBACKS);
    bufferevent_setcb(bev, (bufferevent_data_cb)read_buf_cb
            , NULL, (bufferevent_event_cb)event_cb, (void*)base);
    bufferevent_enable(bev, EV_READ);

    ev_stdin = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST
            , (event_callback_fn)cmd_msg_cb, (void*)bev);

    event_add(ev_stdin, NULL);

    event_base_dispatch(base);
    bufferevent_free(bev);
    event_free(ev_stdin);
    event_base_free(base);
    puts("exit now...");
}

int main(int argc, char** argv)
{
    int clientfd;
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(SERVERPORT);
    clientfd = socket(AF_INET, SOCK_STREAM, 0);
    connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
    main_loop(clientfd);
    return 0;
}

相關文章