libevent庫介紹--事件和數據緩衝

首先在學習libevent庫的使用前,咱們還要從基本的瞭解開始,已經熟悉了epoll以及reactor,而後從event_base學習,依次學習事件event、數據緩衝Bufferevent和數據封裝evBuffer等,再結合具體的幾個實例來了解libevent庫的一些基本使用,有助於咱們理解它的一些內部實現(因爲以前我已經寫過一篇epoll反應堆模型的,因此這裏就再也不介紹,直接從event_base開始介紹)。react

 

libevent特色:後端

  • 事件驅動,高性能;
  • 輕量級,專一於網絡;
  • 跨平臺,支持Windows、Linux、Mac OS等;
  • 支持多種    I/O多路複用技術,    epoll、poll、dev/poll、select    和kqueue    等;
  • 支持    I/O,定時器和信號等事件;

libevent組件:安全

  • evutil:用於抽象不一樣平臺網絡實現差別的通用功能。
  • event和event_base:libevent的核心,爲各類平臺特定的、基於事件的非阻塞 IO後端提供抽象API,讓程序能夠知道套接字什麼時候已經準備好,能夠讀或者寫,而且處理基本的超時功能,檢測OS信號。
  • bufferevent:爲libevent基於事件的核心提供使用更方便的封裝。除了通知程序套接字已經準備好讀寫以外,還讓程序能夠請求緩衝的讀寫操做,能夠知道什麼時候IO已經真正發生。( bufferevent接口有多個後端, 能夠採用系統可以提供的更快的非阻塞 IO方式,如Windows中的IOCP。)
  • evbuffer:在bufferevent層之下實現了緩衝功能,而且提供了方便有效的訪問函數

event_base

  使用 libevent 函數以前須要分配一個或者多個 event_base 結構體。每一個event_base 結構 體持有一個事件集合,能夠檢測以肯定哪一個事件是激活的。若是設置 event_base 使用鎖,則能夠安全地在多個線程中訪問它 。然而,其事件循環只能 運行在一個線程中。若是須要用多個線程檢測 IO,則須要爲每一個線程使用一個 event_base。
  event_base_new()函數分配而且返回一個新的具備默認設置的 event_base。函數會檢測環境變量,返回一個到 event_base 的指針。若是發生錯誤,則返回 NULL。選擇各類方法時,函數會選擇 OS 支持的最快方法。
       struct event_base *event_base_new(void);服務器

  使用完 event_base 以後,使用 event_base_free()進行釋放。
     void event_base_free(struct event_base *base);
       注意:這個函數不會釋放當前與 event_base 關聯的任何事件,或者關閉他們的套接字 ,或 者釋聽任何指針網絡

   一旦有了一個已經註冊了某些事件的 event_base, 就須要讓 libevent 等待事件而且通知事件的發生。
        #define EVLOOP_ONCE 0x01
        #define EVLOOP_NONBLOCK 0x02
        #define EVLOOP_NO_EXIT_ON_EMPTY 0x04
        int event_base_loop(struct event_base *base, int flags);socket

   默認狀況下,event_base_loop()函數運行 event_base 直到其中沒有已經註冊的事件爲止。執行循環的時候 ,函數重複地檢查是否有任何已經註冊的事件被觸發 (好比說,讀事件 的文件描述符已經就緒,能夠讀取了;或者超時事件的超時時間即將到達)。若是有事件被觸發,函數標記被觸發的事件爲 「激活的」,而且執行這些事件。
        在 flags 參數中設置一個或者多個標誌就能夠改變 event_base_loop()的行爲。若是設置了 EVLOOP_ONCE ,循環將等待某些事件成爲激活的 ,執行激活的事件直到沒有更多的事件能夠執行,然會返回。若是設置了 EVLOOP_NONBLOCK,循環不會等待事件被觸發: 循環將僅僅檢測是否有事件已經就緒,能夠當即觸發,若是有,則執行事件的回調。
        完成工做後,若是正常退出, event_base_loop()返回0;若是由於後端中的某些未處理 錯誤而退出,則返回 -1。 函數

   int event_base_dispatch(struct event_base *base);
      event_base_dispatch ()等同於沒有設置標誌的 event_base_loop ( )。因此event_base_dispatch ()將一直運行,直到沒有已經註冊的事件了,或者調用      event_base_loopbreak()或者 event_base_loopexit()爲止。oop

     若是想在移除全部已註冊的事件以前中止活動的事件循環,能夠調用兩個稍有不一樣的函數 。
      int event_base_loopexit(struct event_base *base,const struct timeval *tv);
      int event_base_loopbreak(struct event_base *base);
      event_base_loopexit()讓 event_base 在給定時間以後中止循環。若是 tv 參數爲NULL, event_base 會當即中止循環,沒有延時。
      若是 event_base 當前正在執行任何激活事件的回調,則回調會繼續運行,直到運行完全部激活事件的回調之才退出。
      event_base_loopbreak ()讓 event_base 當即退出循環。它與event_base_loopexit (base,NULL)的不一樣在於,若是 event_base 當前正在執行激活事件的回調 ,它將在執行完當前正在處理的事件後當即退出。性能

綜上所述,event_base處理過程主要以下:學習

  1. 調用event_base_new()建立一個event_base
  2. 註冊了某些事件的 event_base
  3. 調用event_base_loop()或者event_base_dispatch()函數,循環等待事件而且通知事件的發生
  4. 調用event_base_loopexit()或者event_base_loopbreak()移除全部已註冊的事件以前中止活動的事件循環
  5. 使用完 event_base 以後,使用event_base_free()進行釋放

 

事件event

  libevent 的基本操做單元是事件。每一個事件表明一組條件的集合,這些條件包括:

  • 文件描述符已經就緒,能夠讀取或者寫入
  • 文件描述符變爲就緒狀態,能夠讀取或者寫入(僅對於邊沿觸發 IO)
  • 超時事件
  • 發生某信號
  • 用戶觸發事件

  全部事件具備類似的生命週期。調用 libevent 函數設置事件而且關聯到event_base 以後, 事件進入「已初始化(initialized)」狀態。此時能夠將事件添加到event_base 中,這使之進入「未決(pending)」狀態。在未決狀態下,若是觸發事件的條件發生(好比說,文件描述 符的狀態改變,或者超時時間到達 ),則事件進入「激活(active)」狀態,(用戶提供的)事件回調函數將被執行。若是配置爲「持久的(persistent)」,事件將保持爲未決狀態。不然, 執行完回調後,事件再也不是未決的。刪除操做可讓未決事件成爲非未決(已初始化)的 ; 添加操做可讓非未決事件再次成爲未決的。

建立事件

    使用 event_new()接口建立事件。
    #define EV_TIMEOUT 0x01
    #define EV_READ 0x02
    #define EV_WRITE 0x04
    #define EV_SIGNAL 0x08
    #define EV_PERSIST 0x10
    #define EV_ET 0x20
    typedef void (*event_callback_fn)(evutil_socket_t, short, void *); 
    struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event_callback_fn cb,void *arg);
    void event_free(struct event *event);

    event_new()試圖分配和構造一個用於 base 的新的事件。 
      

what 參數是上述標誌的集合。若是 fd 非負,則它是將被觀察其讀寫事件的文件。事件被激活時, libevent 將調用 cb 函數,傳遞這些參數:文件描述符 fd,表示全部被觸發事件的位字段 ,以及構造事件時的 arg參數。發生內部錯誤,或者傳入無效參數時, event_new()將返回 NULL。全部新建立的事件都處於已初始化和非未決狀態 ,調用 event_add()可使其成爲未決的。

釋放事件

    要釋放事件,調用 event_free()。對未決或者激活狀態的事件調用 event_free()是安全的:在釋放事件以前,函數將會使事件成爲非激活和非未決的。

事件標誌

    EV_TIMEOUT:這個標誌表示某超時時間流逝後事件成爲激活的。構造事件的時候,EV_TIMEOUT標誌是 被忽略的:能夠在添加事件的時候設置超時 ,也能夠不設置。超時發生時,回調函數的 what 參數將帶有這個標誌。
    EV_READ:表示指定的文件描述符已經就緒,能夠讀取的時候,事件將成爲激活的。
    EV_WRITE:表示指定的文件描述符已經就緒,能夠寫入的時候,事件將成爲激活的。
    EV_SIGNAL:用於實現信號檢測,請看下面的 「構造信號事件」節。
    EV_PERSIST:表示事件是「持久的」
    EV_ET:表示若是底層的 event_base 後端支持邊沿觸發事件,則事件應該是邊沿觸發的。

事件的未決和非未決 

    設置未決事件:在非未決的事件上調用 event_add()將使其在配置的 event_base 中成爲未決的。
    int event_add(struct event *ev, const struct timeval *tv);
    若是 tv 爲 NULL,添加的事件不會超時。不然, tv 以秒和微秒指定超時值。
    若是對已經未決的事件調用 event_add(),事件將保持未決狀態,並在指定的超時時間被從新調度。

    設置非未決事件:對已經初始化的事件調用 event_del()將使其成爲非未決和非激活的。若是事件不是未決的或者激活的,調用將沒有效果。
    int event_del(struct event *ev);
      若是在事件激活後,其回調被執行前刪除事件,回調將不會執行。

一次觸發事件 

    若是不須要屢次添加一個事件,或者要在添加後當即刪除事件,而事件又不須要是持久的 , 則可使用 event_base_once()
    int event_base_once(struct event_base *, evutil_socket_t, short,void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
      除了不支持 EV_SIGNAL 或者 EV_PERSIST 以外,這個函數的接口與 event_new()相同。 安排的事件將以默認的優先級加入到 event_base並執行。回調被執行後,libevent內部將 會釋放 event 結構。

事件總結:1.建立事件

     2.事件的未決和非未決   設置未決事件調用event_add()    設置非未決事件調用event_del()

     3.觸發事件 一次觸發事件調用event_base_once()

event代碼示例

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <event2/event.h>

//typedef void (*event_callback_fn)(evutil_socket_t, short, void *)
void callback_func(evutil_socket_t fd, short event, void *arg)
{
    char buf[256] = {0};
    int len = 0;

    printf("fd = %d, event = %d", fd, event);

    len = read(fd, buf, sizeof(buf));

    if (len == -1) {
        perror("read");
        return;
    } else if (len == 0) {
        perror("remote close fd");
        return;
    } else {
        buf[len] = '\0';
        printf("read buf=[%s]\n", buf);
    }
    return; 
}

int main(int argc, char *argv[])
{
    int fd;
    struct event_base * base = NULL;
    struct event *evfifo = NULL;
    const char *fifo = "event.fifo";

    unlink(fifo);

    if (mkfifo(fifo, 0644) == -1) {
        perror("mkfifo");
        exit(1);
    }

    fd = open(fifo, O_RDONLY);
    if (fd == -1) {
        perror("open socket error");
        exit(1);
    }

    //建立一個event_base 
    base = event_base_new(); 

    //將fd 綁定到一個事件中  同時綁定一個讀的回調函數
    //此時evfifo事件是一個初始化狀態的事件
    evfifo = event_new(base, fd, EV_READ|EV_PERSIST, callback_func, NULL); 

    //將此事件從 初始化--->未決
    event_add(evfifo, NULL);

    //循環等待事件監控
    event_base_dispatch(base);

    event_free(evfifo);
    event_base_free(base);

    return 0;
}    

client.c 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd = 0;

    const char *str = "hello event";

    fd = open("event.fifo", O_RDWR);
    if (fd == -1) {
        perror("open event.fifo error");
        exit(1);
    }

    while (1) {
       write(fd, str, strlen(str));
       sleep(1);
    }

    close(fd);
    return 0;
}

 

    執行以下命令進行編譯(前提已經安裝libevent庫)

    gcc server.c -l event -o server

    gcc client.c -l event -o client

    編譯好後啓動server和client,結果以下:

    

 

數據緩衝bufferevent  

不少時候,除了響應事件以外,應用還但願作必定的數據緩衝。好比說,寫入數據的時候 ,一般的運行模式是:    

      1.決定要向鏈接寫入一些數據,把數據放入到緩衝區中
      2.等待鏈接能夠寫入
      3.寫入儘可能多的數據
      4.記住寫入了多少數據,若是還有更多數據要寫入,等待鏈接再次能夠寫入
  bufferevent 由一個底層的傳輸端口(如套接字 ),一個讀取緩衝區和一個寫入緩衝區組成。與一般的事件在底層傳輸端口已經就緒,能夠讀取或者寫入的時候執行回調不一樣的是,bufferevent 在讀取或者寫入了足夠量的數據以後調用用戶提供的回調。
  bufferevent和evbuffer
    每一個 bufferevent 都有一個輸入緩衝區和一個輸出緩衝區 ,它們的類型都是「structevbuffer」。 有數據要寫入到 bufferevent 時,添加數據到輸出緩衝區 ;bufferevent 中有數據供讀取的時候,從輸入緩衝區抽取(drain)數據。 evbuffer 接口支持不少種操做。

  水位

    每一個 bufferevent 有兩個數據相關的回調:一個讀取回調和一個寫入回調。默認狀況下,從底層傳輸端口讀取了任意量的數據以後會調用讀取回調 ;輸出緩衝區中足夠量的數據被清空到底層傳輸端口後寫入回調會被調用。經過調整 bufferevent 的讀取和寫入 「水位 (watermarks )」能夠覆蓋這些函數的默認行爲。
    每一個 bufferevent 有四個水位:
        讀取低水位 : 讀取操做使得輸入緩衝區的數據量在此級別或者更高時 ,讀取回調將被調用。默認值爲 0,因此每一個讀取操做都會致使讀取回調被調用。
        讀取高水位 : 輸入緩衝區中的數據量達到此級別後, bufferevent 將中止讀取,直到輸入緩衝區中足夠量的數據被抽取 ,使得數據量低於此級別 。默認值是無限 ,因此永遠不會由於輸入緩衝區的大小而中止讀取。
        寫入低水位 : 寫入操做使得輸出緩衝區的數據量達到或者低於此級別時 ,寫入回調將被調用。默認值是 0,因此只有輸出緩衝區空的時候纔會調用寫入回調。
        寫入高水位 : bufferevent 沒有直接使用這個水位。它在 bufferevent 用做另一 個 bufferevent 的底層傳輸端口時有特殊意義。

  回調

     默認狀況下,bufferevent 的回調在相應的條件發生時當即被執行 。在依賴關係複雜的狀況下 ,這種當即調用會製造麻煩 。好比說,假如某個回調在 evbuffer A 空的時候向其中移入數據 ,而另外一個回調在
evbuffer A 滿的時候從中取出數據。這些調用都是在棧上發生的,在依賴關係足夠複雜的時候,有棧溢出的風險。
        要解決此問題,能夠請求 bufferevent(或者 evbuffer)延遲其回調。條件知足時,延遲迴調不會當即調用,而是在 event_loop()調用中被排隊,而後在一般的事件回調以後執行.

  bufferevent 選項標誌

      BEV_OPT_CLOSE_ON_FREE : 釋放 bufferevent 時關閉底層傳輸端口。這將關閉底層套接字,釋放底層 bufferevent 等
           BEV_OPT_THREADSAFE : 自動爲 bufferevent 分配鎖,這樣就能夠安全地在多個線程中使用 bufferevent。
           BEV_OPT_DEFER_CALLBACKS : 設置這個標誌時, bufferevent 延遲全部回調.
           BEV_OPT_UNLOCK_CALLBACKS : 默認狀況下,若是設置 bufferevent 爲線程安全 的,則 bufferevent 會在調用用戶提供的回調時進行鎖定。設置這個選項會讓 libevent 在執行回調的時候不進行鎖定。

  建立基於套接字的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。

     在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操做
       void bufferevent_free(struct bufferevent *bev);

     這個函數釋放 bufferevent。bufferevent 內部具備引用計數,因此,若是釋放 時還有未決的延遲迴調,則在回調完成以前 bufferevent 不會被刪除。
       若是設置了 BEV_OPT_CLOSE_ON_FREE 標誌,而且 bufferevent 有一個套接字或者底層 bufferevent 做爲其傳輸端口,則釋放 bufferevent 將關閉這個傳輸端口。

  操做回調和啓用/禁用

     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 上當前啓用的事件。

  操做bufferevent中的數據
          經過bufferevent獲得evbuffer
          struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
          struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
          這兩個函數提供了很是強大的基礎 :它們分別返回輸入和輸出緩衝區 。
          若是寫入操做由於數據量太少而中止(或者讀取操做由於太多數據而中止 ),則向輸出緩衝 區添加數據(或者從輸入緩衝區移除數據)將自動重啓操做。
   向bufferevent的輸出緩衝區添加數據
        int bufferevent_write(struct bufferevent *bufev,const void *data, size_t size);
        int bufferevent_write_buffer(struct bufferevent *bufev,struct evbuffer *buf);

     這些函數向 bufferevent 的輸出緩衝區添加數據。 bufferevent_write()將內存中從data 處開 始的 size 字節數據添加到輸出緩衝區的末尾 。
          bufferevent_write_buffer()移除 buf 的全部內 容,將其放置到輸出緩衝區的末尾。成功時這些函數都返回 0,發生錯誤時則返回-1。

  從bufferevent的輸入緩衝區移除數據

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

      這些函數從 bufferevent 的輸入緩衝區移除數據。bufferevent_read()至多從輸入緩衝區移除 size 字節的數據,將其存儲到內存中 data 處。函數返回實際移除的字節數。 bufferevent_read_buffer()函數抽空輸入緩衝區的全部內容,將其放置到 buf 中,成功時返 回0,失敗時返回 -1。

  bufferevent的清空操做

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

數據緩衝Bufferevent總結:1.讀取寫入的高低水位

              2.調用bufferevent_socket_new()建立基於套接字的 bufferevent

              3.調用bufferevent_socket_connect啓動連接

              4.操做回調和啓用/禁用

              5.操做bufferevent中的數據

              6.bufferevent的清空操做

              7.調用bufferevent_free()釋放bufferevent操做
*************************************************華麗的分割線*****************************************************************

數據封裝evBuffer
    libevent 的 evbuffer 實現了爲向後面添加數據和從前面移除數據而優化的字節隊列。evbuffer 用於處理緩衝網絡 IO 的「緩衝」部分。它不提供調度 IO 或者當 IO 就緒時觸發 IO 的 功能:這是 bufferevent 的工做。 

  建立和釋放evbuffer 

    struct evbuffer *evbuffer_new(void);
    void evbuffer_free(struct evbuffer *buf);

      這兩個函數的功能很簡明: evbuffer_new() 分配和返回一個新的空 evbuffer ; evbuffer_free()釋放 evbuffer 和其內容。 

  evbuffer添加數據 

    int evbuffer_add(struct evbuffer *buf, const void *data, size_tdatlen);
      這個函數添加 data 處的 datalen 字節到 buf 的末尾,
      成功時返回0,失敗時返回-1。 

  evbuffer數據移動 

    int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src);
      int evbuffer_remove_buffer(struct evbuffer *src,struct evbuffer *dst,size_t datlen);

      evbuffer_add_buffer()將 src 中的全部數據移動到 dst 末尾,成功時返回0,失敗時返回-1
      evbuffer_remove_buffer()函數從 src 中移動 datlen 字節到 dst 末尾,儘可能少進行復制。若是字節數小於 datlen,全部字節被移動。函數返回移動的字節數。 
*************************************************華麗的分割線*****************************************************************

結合以上介紹,寫一個關於數據緩衝Bufferevent和evbuffer一些操做的案例:使用套接字基於TCP協議,將客戶端將小寫字母發送到服務端,轉換成大寫字母,再回寫給客戶端輸出到屏幕上

  sever.c以下

#include<event2/listener.h>
#include<event2/bufferevent.h>
#include<event2/buffer.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<string.h>
#include<strings.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#define SERV_PORT 9999

//若是客戶端有數據寫過來,那麼會觸發當前的回調函數
static void echo_read_cb(struct bufferevent *bev,void *ctx)
{
    //input 就是當前bufferevent的輸入緩衝區地址,若是想獲得用戶數據就從input中獲取
    struct evbuffer *input = bufferevent_get_input(bev);
    
    //output 就是當前bufferevent的輸出緩衝區地址,若是客戶端寫數據 就將數據寫到output中就能夠了
    struct evbuffer *output = bufferevent_get_output(bev);

    //將input緩衝區中數據取出來進行大小寫轉換
    int i,ret;
    char *buf = malloc(BUFSIZ);
    bzero(buf,BUFSIZ);
    void* ptr = (void*)buf;
    evbuffer_remove(input,ptr,BUFSIZ);//將input緩衝區中數據移除,並複製一份到ptr所指向的空間中
    buf = (char*)ptr;
    for(i = 0;i < strlen(buf); i++ )
    {
        buf[i] = toupper(buf[i]);//大小寫轉換
    }
    ret = evbuffer_add(output,(void*)buf,strlen(buf));//將buf中的數據內容添加到output緩衝區
    if(ret == -1)
    {
        perror("evbuffer_add error");
    }
    free(buf);
}

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);
    }
}

//fd -- 服務器已經accept成功的cfd fd就是能夠直接跟客戶端通訊的套接字
static void accept_conn_cb(struct evconnlistener *listener,evutil_socket_t fd,struct sockaddr *address,int socklen,void *ctx)
{
    struct event_base *base = evconnlistener_get_base(listener);
    //建立一個bufferevent綁定fd 和 base
    struct bufferevent *bev = bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);
    //當前剛建立好的bufferevent事件 註冊一些回調函數
    bufferevent_setcb(bev,echo_read_cb,NULL,echo_event_cb,NULL);
    //啓動監聽bufferevent 讀事件和寫事件
    bufferevent_enable(bev,EV_READ|EV_WRITE);
}

static void accept_error_cb(struct evconnlistener *litener,void *ctx)
{
    struct event_base *base = evconnlistener_get_base(litener);
    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 = SERV_PORT;
    if(argc>1)
    {
        port = atoi(argv[1]);
    }
    if(port<=0||port>65535)
    {
        puts("Invalid port");
        return 1;
    }

    //建立一個 eventbase句柄,在內核開闢一個監聽事件的根節點
    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);

    //將listen fd 封裝一個事件 默認在裏面已經執行了bind和linsten指令
    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;
}

 

 

使用以下命令進行編譯:

          gcc server.c -l event -o server

測試效果:

      啓動服務端,客戶端用命令 nc 127.0.0.1 9999進行啓動

      

***************************************************寫在後面*****************************************************************

這裏只是關於libevent庫的一些簡單使用介紹,介紹了一些經常使用的一些函數和一些名詞術語的意義,若是要詳細的去了解,仍是須要去閱讀libevent庫相關書籍,也能夠對其用法有必定的瞭解後,去研究一下它的源碼,對我的的編碼水平提升會有質的飛躍。

努力不必定有結果,有結果不必定是努力
相關文章
相關標籤/搜索