1. 程式人生 > >linux下libevent的安裝和使用例子:資料回顯

linux下libevent的安裝和使用例子:資料回顯

1、背景介紹

輕量級,開源高效能網路庫。跨平臺,支援Windows、Linux、*BSD和Mac Os;

1)支援使用者三種類型的事件(事件驅動(event-driven)):支援網路I/O,定時器和訊號等事件。定時器的資料結構使用最小堆(Min Heap),以提高效率。網路IO和訊號的資料結構採用了雙向連結串列(TAILQ)。在實現上主要有3種連結串列:EVLIST_INSERTED, EVLIST_ACTIVE, EVLIST_TIMEOUT,一個ev在這3種連結串列之間被插入或刪除,處於EVLIST_ACTIVE連結串列中的ev最後將會被排程執行。

2)支援多種I/O多路複用技術, epoll、poll、dev/poll、select和kqueue等;

3)註冊事件優先順序

libevent預設情況下是單執行緒,每個執行緒有且僅有一個event_base,這對應一個struct event_base結構體(以及附於其上的事件管理器),用來schedule託管給它的一系列event,可以和作業系統的程序管理類比,當然,要更簡單一點。當一個事件發生後,event_base會在合適的時間(不一定是立即)去呼叫繫結在這個事件上的函式(傳入一些預定義的引數,以及在繫結時指定的一個引數),直到這個函式執行完,再返回schedule其他事件。

2、安裝

tar zxvf libevent-2.0.22-stable.tar.gz

cd libevent-2.0.22-stable
./configure -prefix=/usr/libevent
make

sudo make install

安裝之後,再重啟下。

也可以採用以下:

apt-cache search libevent
和
apt-get install libevent-dev
進行安裝。

3、linux下用qtcreator進行程式設計的時候注意點

在安裝之後,利用Qtcreator進行專案管理的時候,需要在pro檔案中新增如下:


正如在gcc中編譯的時候,新增如下:

gcc -o basic basic.c -levent

否則會出現未定義的情況。

4、使用例子(伺服器回顯):

1)使用Libevent的基本流程
(1)
建立socketbindlisten,設定為非阻塞模式 (2)首先建立一個event_base
物件
//建立一個event_base
struct event_base *base = event_base_new();
assert(base != NULL);
struct event_base *base = event_base_new()用以建立一個事件處理的全域性變數,可以理解為這是一個負責集中處理各種出入IO事件的總管家,它負責接收和派發所有輸入輸出IO事件的資訊,這裡呼叫的是函式event_base_new(), 很多程式裡這裡用的是event_init(),區別就是前者是執行緒安全的、而後者是非執行緒安全的,後者在其官方說明中已經被標誌為過時的函式、且建議用前者代替,libevent中還有很多類似的函式,比如建議用event_base_dispatch代替event_dispatch,用event_assign代替event_set和event_base_set等,關於libevent介面的詳細說明見其官方說明libevent_doc。

event_base內部有一個迴圈,迴圈阻塞在epoll/kqueue等系統呼叫上,直到有一個或者一些事件發生,然後去處理這些事件。當然,這些事件要被繫結在這個event_base上。每個事件對應一個struct event,可以是監聽一個fd或者POSIX訊號量之類。struct event使用event_new來建立和繫結,使用event_add來啟用:

(3)建立一個event物件,並且將其監聽的socket託管給event_base,指定要監聽的事件型別,並綁上相應的回撥函式
//建立並繫結一個event
struct event *listen_event;
//引數:event_base, 監聽的fd,事件型別及屬性,繫結的回撥函式,給回撥函式的引數
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);

(4)通過event_add方法啟動監聽事件
//引數:event,超時時間(struct timeval *型別的,NULL表示無超時設定)
event_add(listen_event, NULL);

(5)進入事件迴圈 需要啟動event_base的迴圈,這樣才能開始處理髮生的事件。迴圈的啟動使用event_base_dispatch,迴圈將一直持續,直到不再有需要關注的事件,或者是遇到event_loopbreak()/event_loopexit()函式。
//啟動事件迴圈
event_base_dispatch(base);

接下來關注下繫結到event的回撥函式callback_func:傳遞給它的是一個socket fd、一個event型別及屬性bit_field、以及傳遞給event_new的最後一個引數(去上面幾行回顧一下,把event_base給傳進來了,實際上更多地是分配一個結構體,把相關的資料都撂進去,然後丟給event_new,在這裡就能取得到了)。其原型是:
typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)

小結下:
對於一個伺服器而言,上面的流程大概是這樣組合的:
    a. listener = socket(),bind(),listen(),設定nonblocking(POSIX系統中可使用fcntl設定,windows不需要設定,實際上libevent提供了統一的包裝evutil_make_socket_nonblocking)
    b. 建立一個event_base
    c. 建立一個event,將該socket託管給event_base,指定要監聽的事件型別,並繫結上相應的回撥函式(及需要給它的引數)。對於listener socket來說,只需要監聽EV_READ|EV_PERSIST
    d. 啟用該事件
    e. 進入事件迴圈
     f.  (非同步) 當有client發起請求的時候,呼叫該回調函式,進行處理。

整理後的完整程式碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <event.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <event2/event.h>
#include <event2/bufferevent.h>

#define PORT 25341
#define BACKLOG 5
#define MEM_SIZE 1024
struct event_base* base;
struct sock_ev {
    struct event* read_ev;
    struct event* write_ev;
    char* buffer;
};

void release_sock_event(struct sock_ev* ev)
{
    event_del(ev->read_ev);
    free(ev->read_ev);
    free(ev->write_ev);
    free(ev->buffer);
    free(ev);
}

void on_accept(int sock, short event, void* arg);
void on_read(int sock, short event, void* arg);
void on_write(int sock, short event, void* arg);

int main(int argc, char* argv[])
{
    struct sockaddr_in my_addr;
    int sock;
    //建立套接字描述符,實質是一個檔案描述符
    //AF_INET表示使用IP地址,SOCK_STREAM表示使用流式套接字
    sock = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
    memset(&my_addr, 0, sizeof(my_addr));

    //例項化物件的屬性
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;

    //將套接字地址和套接字描述符繫結起來
    bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));
    //監聽該套接字,連線的客戶端數量最多為BACKLOG
    listen(sock, BACKLOG);

    //宣告事件
    struct event listen_ev;
    //建立基事件
    base = event_base_new();
    //設定回撥函式.將event物件監聽的socket託管給event_base,指定要監聽的事件型別,並綁上相應的回撥函式
    event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);//
    //上述操作說明在listen_ev這個事件監聽sock這個描述字的讀操作(EV_READ),當讀訊息到達則呼叫on_accept函式,EV_PERSIST引數告訴系統持續的監聽sock上的讀事件,
    //不指定這個屬性的話,回撥函式被觸發後,事件會被刪除.所以,如果不加該引數,每次要監聽該事件時就要重複的呼叫event_add函式,從前面的程式碼可知,
    //sock這個描述字是bind到本地的socket埠上,因此其對應的可讀事件自然就是來自客戶端的連線到達,我們就可以呼叫accept無阻塞的返回客戶的連線了。

    //使從屬於基事件.將listen_ev註冊到base這個事件中,相當於告訴處理IO的管家請留意我的listen_ev上的事件。
    event_base_set(base, &listen_ev);
    //有時候看到使用<span style="color:#FF0000;">event_new</span>(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base)代替event_set和event_base_set這兩個函式

    //新增到事件隊列當中.相當於告訴處理IO的管家,當有我的事件到達時你發給我(呼叫on_accept函式),至此對listen_ev的初始化完畢
    event_add(&listen_ev, NULL);
    //開始迴圈.正式啟動libevent的事件處理機制,使系統執行起來.event_base_dispatch是一個無限迴圈
    event_base_dispatch(base);
    return 0;
}

void on_accept(int sock, short event, void* arg)
{
    struct sockaddr_in cli_addr;
    int newfd;
    socklen_t sin_size;
    // read_ev must allocate from heap memory, otherwise the program would crash from segmant fault
    struct event* read_ev = (struct event*)malloc(sizeof(struct event));
    sin_size = sizeof(struct sockaddr_in);
    newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);//指定服務端去接受客戶端的連線
    //客戶的描述字newfd上監聽可讀事件,當有資料到達是呼叫on_read函式
    event_set(read_ev, newfd, EV_READ|EV_PERSIST, on_read, read_ev);
    event_base_set(base, read_ev);
    event_add(read_ev, NULL);
    //這裡需要注意兩點,一是read_ev需要從堆裡malloc出來,如果是在棧上分配,那麼當函式返回時變數佔用的記憶體會被釋放,
    //因此事件主迴圈event_base_dispatch會訪問無效的記憶體而導致程序崩潰(即crash);第二個要注意的是event_set中,read_ev作為引數傳遞給了on_read函式
}

void on_read(int sock, short event, void* arg)
{
    struct event* write_ev;
    int size;
    char* buffer = (char*)malloc(MEM_SIZE);
    bzero(buffer, MEM_SIZE);
    size = recv(sock, buffer, MEM_SIZE, 0);
    printf("receive data:%s, size:%d\n", buffer, size);
    if (size == 0)//當從socket讀返回0標誌,對方已經關閉了連線,因此這個時候就沒必要繼續監聽該套介面上的事件
    {
        event_del((struct event*)arg);
        //由於EV_READ在on_accept函式裡是用EV_PERSIST引數註冊的,因此要顯示的呼叫event_del函式取消對該事件的監聽
        free((struct event*)arg);
        close(sock);
        return;
    }
    write_ev = (struct event*) malloc(sizeof(struct event));
    event_set(write_ev, sock, EV_WRITE, on_write, buffer);//寫時呼叫on_write函式,注意將buffer作為引數傳遞給了on_write
    event_base_set(base, write_ev);
    event_add(write_ev, NULL);
}
// on_write函式中向客戶端回寫資料,然後釋放on_read函式中malloc出來的buffer。在很多書合編程指導中都很強調資源的所有權,經常要求誰分配資源、就由誰釋放資源,
//這樣對資源的管理指責就更明確,不容易出問題,但是通過該例子我們發現在非同步程式設計中資源的分配與釋放往往是由不同的所有者操作的,因此也是比較容易出問題的地方。
void on_write(int sock, short event, void* arg)
{
    char* buffer = (char*)arg;
    send(sock, buffer, strlen(buffer), 0);
    free(buffer);
}

再來看看前面提到的on_read函式中存在的問題,首先write_ev是動態分配的記憶體,但是沒有釋放,因此存在記憶體洩漏,另外,on_read中進行malloc操作,那麼當多次呼叫該函式的時候就會造成記憶體的多次洩漏。這裡的解決方法是對socket的描述字可以封裝一個結構體來保護讀、寫的事件以及資料緩衝區,

其實在on_read函式中從socket讀取資料後程序就可以直接呼叫write/send介面向客戶回寫資料了,因為寫事件已經滿足,不存在非同步不非同步的問題,這裡進行on_write的非同步操作僅僅是為了說明非同步程式設計中資源的管理與釋放的問題,另外一方面,直接呼叫write/send函式向客戶端寫資料可能導致程式較長時間阻塞在IO操作上,比如socket的輸出緩衝區已滿,則write/send操作阻塞到有可用的緩衝區之後才能進行實際的寫操作,而通過向寫事件註冊on_accept函式,那麼libevent會在合適的時間呼叫我們的callback函式,(比如對於會引起IO阻塞的情況比如socket輸出緩衝區滿,則由libevent設計演算法來處理,如此當回撥on_accept函式時我們在呼叫IO操作就不會發生真正的IO之外的阻塞)。注:前面括號中是我個人認為一個庫應該實現的功能,至於libevent是不是實現這樣的功能並不清楚也無意深究。

2)Libevent buffer實現非同步傳輸
Linux下有epollBSDSkqueueSolarisevport/dev/poll等等可以實現非同步傳輸,但是沒有哪一個作業系統擁有他們全部,而libevent就是把這些介面都封裝起來,並且無論哪一個系統使用它都是最高效的。

Socketread操作的資料先全部儲存到input buffer當中,然後再儲存到記憶體當中 Socketwrite操作的資料全部從記憶體輸出到output buffer當中
bufferevent是個神器。struct bufferevent內建了兩個event(read/write)和對應的緩衝區【struct evbuffer *input, *output】,並提供相應的函式用來操作緩衝區(或者直接操作bufferevent)。每當有資料被讀入input的時候,read_cb函式被呼叫;每當output被輸出完的時候,write_cb被呼叫;在網路IO操作出現錯誤的情況(連線中斷、超時、其他錯誤),error_cb被呼叫。於是上一部分的步驟被簡化為:
    1. 設定sockfd為nonblocking
    2. 使用bufferevent_socket_new建立一個struct bufferevent *bev,關聯該sockfd,託管給event_base
    3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)將EV_READ/EV_WRITE對應的函式
    4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)來啟用read/write事件
    ------
    5. (非同步)
        在read_cb裡面從input讀取資料,處理完畢後塞到output裡(會被自動寫入到sockfd)
        在write_cb裡面(需要做什麼嗎?對於一個echo server來說,read_cb就足夠了)
        在error_cb裡面處理遇到的錯誤
    *. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)來設定讀寫超時, 在error_cb裡面處理超時。
    *. read_cb和write_cb的原型是
        void read_or_write_callback(struct bufferevent *bev, void *arg)
      error_cb的原型是
        void error_cb(struct bufferevent *bev, short error, void *arg) //這個是event的標準回撥函式原型
      可以從bev中用libevent的API提取出event_base、sockfd、input/output等相關資料,
於是程式碼簡化到只需要幾行的read_cb和error_cb函式即可:
void read_cb(struct bufferevent *bev, void *arg) {
    char line[256];
    int n;
    evutil_socket_t fd = bufferevent_getfd(bev);
    while (n = bufferevent_read(bev, line, 256), n > 0)
        bufferevent_write(bev, line, n);
}

void error_cb(struct bufferevent *bev, short event, void *arg) {
    bufferevent_free(bev);
}
綜合上述,程式碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>

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

#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32

void do_accept(evutil_socket_t listener, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);
void write_cb(struct bufferevent *bev, void *arg);

int main()
{
    //int ret;
    evutil_socket_t listener;
    listener = socket(AF_INET, SOCK_STREAM, 0);
    assert(listener > 0);
    evutil_make_listen_socket_reuseable(listener);

    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    sin.sin_port = htons(LISTEN_PORT);

    if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        perror("bind");
        return 1;
    }

    if (listen(listener, LISTEN_BACKLOG) < 0) {
        perror("listen");
        return 1;
    }

    printf ("Listening...\n");

    evutil_make_socket_nonblocking(listener);

    struct event_base *base = event_base_new();
    assert(base != NULL);
    struct event *listen_event;
    listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
    event_add(listen_event, NULL);
    event_base_dispatch(base);

    printf("The End.");
    return 0;
}

void do_accept(evutil_socket_t listener, short event, void *arg)
{
    struct event_base *base = (struct event_base *)arg;
    evutil_socket_t fd;
    struct sockaddr_in sin;
    socklen_t slen = sizeof(sin);
    fd = accept(listener, (struct sockaddr *)&sin, &slen);
    if (fd < 0) {
        perror("accept");
        return;
    }
    if (fd > FD_SETSIZE) { //這個if是參考了那個ROT13的例子,貌似是官方的疏漏,從select-based例子裡抄過來忘了改
        perror("fd > FD_SETSIZE\n");
        return;
    }

    printf("ACCEPT: fd = %u\n", fd);

    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
    bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
}

void read_cb(struct bufferevent *bev, void *arg)
{
#define MAX_LINE    256
    char line[MAX_LINE+1];
    int n;
    evutil_socket_t fd = bufferevent_getfd(bev);

    while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
        line[n] = '\0';
        printf("fd=%u, read line: %s\n", fd, line);

        bufferevent_write(bev, line, n);
    }
}

void write_cb(struct bufferevent *bev, void *arg) {}

void error_cb(struct bufferevent *bev, short event, void *arg)
{
    evutil_socket_t fd = bufferevent_getfd(bev);
    printf("fd = %u, ", fd);
    if (event & BEV_EVENT_TIMEOUT) {
        printf("Timed out\n"); //if bufferevent_set_timeouts() called
    }
    else if (event & BEV_EVENT_EOF) {
        printf("connection closed\n");
    }
    else if (event & BEV_EVENT_ERROR) {
        printf("some other error\n");
    }
    bufferevent_free(bev);
}

為完整起見,在此分別附上客戶端和服務端的程式碼。

服務端程式碼:

//server端的程式碼

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>

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

#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
#define MAX_LINE    256

void do_accept(evutil_socket_t listener, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);
void write_cb(struct bufferevent *bev, void *arg);

int main()
{
    //int ret;
    evutil_socket_t listener;//用於跨平臺表示socket的ID(在Linux下表示的是其檔案描述符)
    listener = socket(AF_INET, SOCK_STREAM, 0);
    assert(listener > 0);
    //用於跨平臺將socket設定為可重用(實際上是將埠設為可重用
    evutil_make_listen_socket_reuseable(listener);

    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    sin.sin_port = htons(LISTEN_PORT);

    if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        perror("bind");
        return 1;
    }

    if (listen(listener, LISTEN_BACKLOG) < 0) {
        perror("listen");
        return 1;
    }

    printf ("Listening...\n");
    /* 用於跨平臺將socket設定為非阻塞,使用bufferevent需要 */
    evutil_make_socket_nonblocking(listener);
    //主要記錄事件的相關屬性
    struct event_base *base = event_base_new();
    assert(base != NULL);
    /* Register listen event. */
    struct event *listen_event;
    listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
    event_add(listen_event, NULL);
    /* Start the event loop. */
    event_base_dispatch(base);

    printf("The End.");
    //close(listener);
    return 0;
}

void do_accept(evutil_socket_t listener, short event, void *arg)
{
    struct event_base *base = (struct event_base *)arg;
    evutil_socket_t fd;
    struct sockaddr_in sin;
    socklen_t slen = sizeof(sin);
    fd = accept(listener, (struct sockaddr *)&sin, &slen);
    if (fd < 0) {
        perror("accept");
        return;
    }
    if (fd > FD_SETSIZE) { //這個if是參考了那個ROT13的例子,貌似是官方的疏漏,從select-based例子裡抄過來忘了改
        perror("fd > FD_SETSIZE\n");
        return;
    }

    printf("ACCEPT: fd = %u\n", fd);
    //關聯該sockfd,託管給event_base.
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    //設定讀寫對應的回撥函式
    bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
    //啟用讀寫事件,其實是呼叫了event_add將相應讀寫事件加入事件監聽佇列poll.
    //如果相應事件不置為true,bufferevent是不會讀寫資料的
    bufferevent_enable(bev, EV_READ|EV_PERSIST);
//    進入bufferevent_setcb回撥函式:
//    在readcb裡面從input中讀取資料,處理完畢後填充到output中;
//    writecb對於服務端程式,只需要readcb就可以了,可以置為NULL;
//    errorcb用於處理一些錯誤資訊
}

void read_cb(struct bufferevent *bev, void *arg)
{
    char line[MAX_LINE+1];
    int n;
    evutil_socket_t fd = bufferevent_getfd(bev);

    while (n = bufferevent_read(bev, line, MAX_LINE), n > 0)
    {
        line[n] = '\0';
        printf("fd=%u, Server gets the message from client read line: %s\n", fd, line);
	//直接將讀取的結果,不做任何修改(本文是跳過前兩個字元),直接返回給客戶端
	bufferevent_write(bev, line+2, n);//方案1
    }
}

void write_cb(struct bufferevent *bev, void *arg)
{
 printf("HelloWorld\n");//直接空程式碼即可,因為這裡並不會被觸發呼叫
}

void error_cb(struct bufferevent *bev, short event, void *arg)
{
    evutil_socket_t fd = bufferevent_getfd(bev);
    printf("fd = %u, ", fd);
    if (event & BEV_EVENT_TIMEOUT) {
        printf("Timed out\n"); //if bufferevent_set_timeouts() called
    }
    else if (event & BEV_EVENT_EOF) {
        printf("connection closed\n");
    }
    else if (event & BEV_EVENT_ERROR) {
        printf("some other error\n");
    }
    bufferevent_free(bev);
}



客戶端程式碼:

//客戶端程式碼
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>

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

#define SERV_PORT 9999
#define MAX_LINE 1024

void cmd_msg_cb(int fd, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        perror("usage: echocli <IPadress>");
        return 1;
    }

    evutil_socket_t sockfd;
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket\n");
        return 1;
    }

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 1)
    {
        perror("inet_ntop\n");
        return 1;
    }
    if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect\n");
        return 1;
    }
    evutil_make_socket_nonblocking(sockfd);

    printf("Connect to server sucessfully!\n");
    // build event base
    struct event_base *base = event_base_new();
    if(base == NULL)
    {
        perror("event_base\n");
        return 1;
    }
    const char *eventMechanism = event_base_get_method(base);
    printf("Event mechanism used is %s\n", eventMechanism);
    printf("sockfd = %d\n", sockfd);

    struct bufferevent *bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
    
    struct event *ev_cmd;
    ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb, (void *)bev);
    event_add(ev_cmd, NULL);
    
    bufferevent_setcb(bev, read_cb, NULL, error_cb, (void *)ev_cmd);
    bufferevent_enable(bev, EV_READ | EV_PERSIST);
    
    event_base_dispatch(base);

    printf("The End.");
    return 0;
}

void cmd_msg_cb(int fd, short event, void *arg)
{
    char msg[MAX_LINE];
    int nread = read(fd, msg, sizeof(msg));
    if(nread < 0)
    {
        perror("stdio read fail\n");
        return;
    }

    struct bufferevent *bev = (struct bufferevent *)arg;
    bufferevent_write(bev, msg, nread);
}

void read_cb(struct bufferevent *bev, void *arg)
{
    char line[MAX_LINE + 1];
    int n;
    evutil_socket_t fd = bufferevent_getfd(bev);

    while((n = bufferevent_read(bev, line, MAX_LINE)) > 0)
    {
        line[n] = '\0';
        printf("fd = %u, read from server: %s", fd, line);
    }
}

void error_cb(struct bufferevent *bev, short event, void *arg)
{
    evutil_socket_t fd = bufferevent_getfd(bev);
    printf("fd = %u, ", fd);
    if(event & BEV_EVENT_TIMEOUT)
        printf("Time out.\n");  // if bufferevent_set_timeouts() is called
    else if(event & BEV_EVENT_EOF)
        printf("Connection closed.\n");
    else if(event & BEV_EVENT_ERROR)
        printf("Some other error.\n");
    bufferevent_free(bev);

    struct event *ev = (struct event *)arg;
    event_free(ev);
}


執行結果如下所示:本文為了方便區別,將客戶端傳送到服務端再回顯到客戶端的過程,在從服務端回顯到客戶端的時候,去掉前兩個字元。如,客戶端輸入1234567,則從服務端讀取到的資料為34567。

客戶端的輸入和輸出結果:


服務端的輸入和輸出結果:


參考:

http://www.cnblogs.com/cnspace/archive/2011/07/19/2110891.html

http://www.felix021.com/blog/read.php?2068

http://www.bkjia.com/ASPjc/886985.html

http://blog.csdn.net/tujiaw/article/details/8872943

http://popozhu.github.io/2013/06/26/libevent_r5_bufferevent%E5%9F%BA%E7%A1%80%E5%92%8C%E6%A6%82%E5%BF%B5/

http://blog.csdn.net/bestone0213/article/details/46729091

https://www.ibm.com/developerworks/cn/aix/library/au-libev/