Libevent應用 (二) 與事件一起工作

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

2 與事件一起工作

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

​ (1)檔案描述符已經就緒,可以讀取或者寫入

​ (2)檔案描述符變為就緒狀態,可以讀取或者寫入(僅對於邊沿觸發IO)

​ (3)超時事件

​ (4)發生某訊號

​ (5)使用者觸發事件

​ 所有事件具有相似的生命週期。呼叫libevent函式設定事件並且關聯到event_base之後,事件進入“已初始化(initialized)”狀態。此時可以將事件新增到event_base中,這使之進入“未決(pending)”狀態。

​ 在未決狀態下,如果觸發事件的條件發生(比如說,檔案描述符的狀態改變,或者超時時間到達),則事件進入“啟用(active)”狀態,(使用者提供的)事件回撥函式將被執行。

​ 如果配置為“持久的(persistent)”,事件將保持為未決狀態。否則,執行完回撥後,事件不再是未決的。刪除操作可以讓未決事件成為非未決(已初始化)的;新增操作可以讓非未決事件再次成為未決的。

2.1 建立事件

​ 使用event_new()介面建立事件。

struct event *event_new(struct event_base *base, evutil_socket_t fd,
    short what, event_callback_fn cb, void *arg);

/*這個標誌表示某超時時間流逝後事件成為啟用的。超時發生時,回撥函式的what引數將帶有這個標誌。*/
#define EV_TIMEOUT      0x01

/*表示指定的檔案描述符已經就緒,可以讀取的時候,事件將成為啟用的。*/
#define EV_READ         0x02

/*表示指定的檔案描述符已經就緒,可以寫入的時候,事件將成為啟用的。*/
#define EV_WRITE        0x04

#define EV_SIGNAL       0x08 //用於實現訊號檢測
#define EV_PERSIST      0x10 //表示事件是“持久的”
#define EV_ET           0x20 //表示如果底層的event_base後端支援邊沿觸發事件,則事件應該是邊沿觸發的。這個標誌影響EV_READ和EV_WRITE的語義。


typedef void (*event_callback_fn)(evutil_socket_t fd, short what, void * arg);
    
void event_free(struct event *event);

​ event_new()試圖分配和構造一個用於base的新事件。what引數是上述標誌的集合。如果fd非負,則它是將被觀察其讀寫事件的檔案。事件被啟用時,libevent將呼叫cb函式,傳遞這些引數:檔案描述符fd,表示所有被觸發事件的位欄位,以及構造事件時的arg引數。

​ 發生內部錯誤,或者傳入無效引數時,event_new()將返回NULL。

​ 要釋放事件,呼叫event_free()。

​ 使用event_assign二次修改event的相關引數:

int event_assign(struct event *event, struct event_base *base,
    evutil_socket_t fd, short what,
    void (*callback)(evutil_socket_t, short, void *), void *arg);

​ 除了event引數必須指向一個未初始化的事件之外,event_assign()的引數與event_new()的引數相同。成功時函式返回0,如果發生內部錯誤或者使用錯誤的引數,函式返回-1。

警告:

不要對已經在event_base中未決的事件呼叫event_assign(),這可能會導致難以診斷的錯誤。如果已經初始化和成為未決的,呼叫event_assign()之前需要呼叫event_del()。

2.2 讓事件未決和非未決

2.2.1 讓事件未決

​ 所有新建立的事件都處於已初始化和非未決狀態,呼叫event_add()可以使其成為未決的。

int event_add(struct event *ev, const struct timeval *tv);

​ 在非未決的事件上呼叫event_add()將使其在配置的event_base中成為未決的。成功時函式返回0,失敗時返回-1。如果tv為NULL,新增的事件不會超時。否則,tv以秒和微秒指定超時值。

​ 如果對已經未決的事件呼叫event_add(),事件將保持未決狀態,並在指定的超時時間被重新排程。

2.2.2 讓事件非未決

int event_del(struct event *ev);

​ 對已經初始化的事件呼叫event_del()將使其成為非未決和非啟用的。如果事件不是未決的或者啟用的,呼叫將沒有效果。成功時函式返回0,失敗時返回-1。

2.3 設定事件的優先順序

​ 多個事件同時觸發時,libevent沒有定義各個回撥的執行次序。可以使用優先順序來定義某些事件比其他事件更重要。

int event_priority_set(struct event *event, int priority);

示例程式碼:

#include <event2/event.h>

void read_cb(evutil_socket_t, short, void *);
void write_cb(evutil_socket_t, short, void *);

void main_loop(evutil_socket_t fd)
{
  struct event *important, *unimportant;
  struct event_base *base;
  base = event_base_new();
  event_base_priority_init(base, 2);
  
  important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL);
  unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);
  event_priority_set(important, 0);
  event_priority_set(unimportant, 1);
}

2.4 關於事件永續性

​ 預設情況下,每當未決事件成為啟用的(因為fd已經準備好讀取或者寫入,或者因為超時),事件將在其回撥被執行前成為非未決的。

​ 如果想讓事件再次成為未決的,可以在回撥函式中再次對其呼叫event_add()。然而,如果設定了EV_PERSIST標誌,事件就是持久的。這意味著即使其回撥被啟用,事件還是會保持為未決狀態。如果想在回撥中讓事件成為非未決的,可以對其呼叫event_del()。

​ 每次執行事件回撥的時候,持久事件的超時值會被複位。因此,如果具有EV_READ|EV_PERSIST標誌,以及5秒的超時值,則事件將在以下情況下成為啟用的:

​ 1. 套接字已經準備好被讀取的時候

​ 2. 從最後一次成為啟用的開始,已經逝去5秒

2.5 事件迴圈

​ 一旦有了一個已經註冊了某些事件的event_base,就需要讓libevent等待事件並且通知事件的發生。

2.5.1 啟動事件迴圈

#define EVLOOP_ONCE             0x01
#define EVLOOP_NONBLOCK         0x02
int event_base_loop(struct event_base *base, int flags);
int event_base_dispatch(struct event_base *base);

​ 預設情況下,event_base_loop()函式執行event_base直到其中沒有已經註冊的事件為止。

​ 執行迴圈的時候,函式重複地檢查是否有任何已經註冊的事件被觸發(比如說,讀事件的檔案描述符已經就緒,可以讀取了;或者超時事件的超時時間即將到達)。如果有事件被觸發,函式標記被觸發的事件為“啟用的”,並且執行這些事件。

​ 在flags引數中設定一個或者多個標誌就可以改變event_base_loop()的行為。如果設定了EVLOOP_ONCE,迴圈將等待某些事件成為啟用的,執行啟用的事件直到沒有更多的事件可以執行,然後返回。

​ 如果設定了EVLOOP_NONBLOCK,迴圈不會等待事件被觸發:迴圈將僅僅檢測是否有事件已經就緒,可以立即觸發,如果有,則執行事件的回撥。

​ 完成工作後,如果正常退出,event_base_loop()返回0;如果因為後端中的某些未處理錯誤而退出,則返回-1。

​ event_base_dispatch()等同於沒有設定標誌的event_base_loop()。

示例程式碼:

#include <event2/event.h>

void cb_func(evutil_socket_t fd, short what, void *arg)
{
        const char *data = arg;
        printf("Got an event on socket %d:%s%s [%s]\n",
            (int) fd,
            (what&EV_TIMEOUT) ? " timeout" : "",
            (what&EV_READ)    ? " read" : "",
            data);
}

void main_loop(evutil_socket_t fd)
{
        struct event *ev;
        struct timeval five_seconds = {5,0};
        struct event_base *base = event_base_new();
        ev = event_new(base, fd, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func, (char*)"Reading event");
        event_add(ev, &five_seconds);
        event_base_dispatch(base);
}

2.5.2 停止事件迴圈

​ 如果想在移除所有已註冊的事件之前停止活動的事件迴圈,可以呼叫兩個稍有不同的函式。

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_loopexit(base,NULL)和event_base_loopbreak(base)在事件迴圈沒有執行時的行為不同:前者安排下一次事件迴圈在下一輪迴調完成後立即停止(就好像帶EVLOOP_ONCE標誌呼叫一樣);後者卻僅僅停止當前正在執行的迴圈,如果事件迴圈沒有執行,則沒有任何效果。

這兩個函式都在成功時返回0,失敗時返回-1。

示例:立即關閉

#include <event2/event.h>

void cb(int sock, short what, void *arg)
{
    struct event_base *base = arg;
    event_base_loopbreak(base);
}

void main_loop(struct event_base *base, evutil_socket_t watchdog_fd)
{
    struct event *watchdog_event;
    watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base);
    event_add(watchdog_event, NULL);
    event_base_dispatch(base);
}

示例: 執行事件迴圈10秒,然後退出

#include <event2/event.h>

void run_base_with_ticks(struct event_base *base)
{
  struct timeval ten_sec;
  ten_sec.tv_sec = 10;
  ten_sec.tv_usec = 0;
  

  event_base_loopexit(base, &ten_sec);
  event_base_dispatch(base);
  puts("Tick");
}

2.5.3 檢查迴圈是否退出

​ 有時候需要知道對event_base_dispatch()或者event_base_loop()的呼叫是正常退出的,還是因為呼叫event_base_loopexit()或者event_base_break()而退出的。可以呼叫下述函式來確定是否呼叫了loopexit或者break函式。

int event_base_got_exit(struct event_base *base);
int event_base_got_break(struct event_base *base);

​ 這兩個函式分別會在迴圈是因為呼叫event_base_loopexit()或者event_base_break()而退出的時候返回true,否則返回false。下次啟動事件迴圈的時候,這些值會被重設。

2.6 配置一次觸發事件

​ 如果不需要多次新增一個事件,或者要在新增後立即刪除事件,而事件又不需要是持久的,則可以使用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結構。成功時函式返回0,失敗時返回-1。

2.7 檢查事件狀態

​ 有時候需要了解事件是否已經新增,檢查事件代表什麼。

/*event_get_fd()返回為事件配置的檔案描述符*/
evutil_socket_t event_get_fd(const struct event *ev);



/*event_get_base()返回為事件配置的event_base。*/
struct event_base *event_get_base(const struct event *ev);

/*event_get_events()返回事件的標誌(EV_READ、EV_WRITE等)。*/
short event_get_events(const struct event *ev);

/*event_get_callback()和event_get_callback_arg()返回事件的回撥函式及其引數指標。*/
event_callback_fn event_get_callback(const struct event *ev);
void *event_get_callback_arg(const struct event *ev);

/*獲取事件的優先順序*/
int event_get_priority(const struct event *ev);

2.8 tcp示例程式碼

伺服器端: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>
#define SERVERPORT 8888
#define MAXBYTES 1024

void  read_cb(evutil_socket_t clientfd, short what, void* arg)
{
    int i, recvlen;
    char buf[MAXBYTES];
    struct event* ev = (struct event*)arg;
    printf("read_cd clientfd %d\n", clientfd);
    recvlen = read(clientfd, buf, sizeof(buf));
    if(recvlen <= 0)
    {
        puts("the other size close or error occur");
        event_del(ev);
        event_free(ev);
        close(clientfd);    
    }
    for(i = 0; i < recvlen; i++)
    {
        buf[i] = toupper(buf[i]);
    }
    write(clientfd, buf, recvlen);
}


void  accept_cb(evutil_socket_t serverfd, short what, void * arg)
{
    
    struct sockaddr_in clientaddr;
    struct event* ev;
    int clientaddrlen;
    int clientfd;
    puts("Accept client connect");
    clientaddrlen = sizeof(clientaddr);
    bzero((void*)&clientaddr, sizeof(clientaddr));
    clientfd  = accept(serverfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
    printf("recv clientfd %d\n", clientfd);
    ev = event_new((struct event_base*)arg, clientfd, EV_READ  | EV_PERSIST, NULL, NULL);
    event_assign(ev, (struct event_base*)arg, clientfd, EV_READ | EV_PERSIST, (event_callback_fn)read_cb, (void*)ev);
    
    event_add(ev, NULL);

}

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...");
    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>
#define SERVERIP "127.0.0.1"
#define SERVERPORT 8888
#define MAXBYTES 1024
void read_cb(evutil_socket_t clientfd, short what, void* arg)
{
    puts("read clientfd");
    char buf[MAXBYTES];
    int ret;
    ret = read(clientfd, buf, sizeof(buf));
    if(ret <= 0)
    {
        close(clientfd);
        event_base_loopexit((struct event_base*)arg, NULL);
    }
    write(STDOUT_FILENO, buf, ret);
}   

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

void main_loop(int clientfd)
{
    struct event_base* base;
    struct event* ev_socket, *ev_stdin; 
    base = event_base_new();
    ev_stdin = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST
            , (event_callback_fn)cmd_msg_cb, (void*)clientfd);

    ev_socket = event_new(base, clientfd, EV_READ | EV_PERSIST
            , (event_callback_fn)read_cb, (void*)base);

    event_add(ev_socket, NULL);
    event_add(ev_stdin, NULL);
    event_base_dispatch(base);

    puts("event free and exit");
    event_free(ev_socket);
    event_free(ev_stdin);
    event_base_free(base);
}
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;
}

相關文章