歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Libevent初探

Libevent初探

日期:2017/3/1 9:11:28   编辑:Linux編程

Libevent 是一個用C語言編寫的、輕量級的開源高性能網絡庫,主要有以下幾個亮點:事件驅動( event-driven),高性能;輕量級,專注於網絡,不如 ACE 那麼臃腫龐大;源代碼相當精煉、易讀;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多種 I/O 多路復用技術, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定時器和信號等事件;注冊事件優先級。

  Libevent 已經被廣泛的應用,作為底層的網絡庫;比如 memcached、 Vomit、 Nylon、 Netchat等等。Libevent之於C語言網絡編程,類似於Nettty之於Java Web編程。學習Netty的小伙伴,不防看下Libevent的實現,會加深對Netty框架的理解~   Libevent的安裝教程網上較多,LZ在此就不再贅述,下面直接來點干貨-Libevent如何使用。

檢查Libevent支持的IO復用方法

  Libevent作為一個高性能網絡庫,內部封裝了多種IO復用技術,如果想看下Libevent在當前系統下支持哪些IO復用技術呢?
int main(int argc, char **argv)
{
    // 版本信息
    cout << event_get_version() << endl;

    // 所支持的IO復用方法
    const char **methods = event_get_supported_methods();
    for (int i = 0; methods[i] != NULL; i++) {
        cout << methods[i] << endl;
    }

    return 0;
}

輸出結果為:(CentOS7 Clion 2016.1.3環境)

  event_get_supported_methods()函數返回Libevent支持的IO復用方法名稱數組,以NULL結尾。該函數實際返回的是全局變量eventops數組,eventops數組存放的是所有支持的IO復用函數,eventops聲明部分的代碼如下:

/* Array of backends in order of preference. */
/* Libevent通過遍歷eventops數組來選擇其後端IO復用技術,遍歷的順序是從數組的第一個元素開始,
 * 到最後一個元素結束。Linux系統下,默認選擇的後端IO復用技術是epoll。*/
static const struct eventop *eventops[] = {
#ifdef _EVENT_HAVE_EVENT_PORTS
    &evportops,
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
    &kqops,
#endif
#ifdef _EVENT_HAVE_EPOLL
    &epollops,
#endif
#ifdef _EVENT_HAVE_DEVPOLL
    &devpollops,
#endif
#ifdef _EVENT_HAVE_POLL
    &pollops,
#endif
#ifdef _EVENT_HAVE_SELECT
    &selectops,
#endif
#ifdef WIN32
    &win32ops,
#endif
    NULL
}; 

Libevent是如何打日志的

  libevent的錯誤處理底層調用的是va_start/va_end等相關宏,它們所在的頭文件是<stdarg.h>,使用C函數庫提供的這些函數,我們也可以實現一個自己的打日志程序,以下是一個使用va_start/va_end的測試程序:
void log(const char *fmt, ...)
{
    char buff[512];
    va_list ap;

    va_start(ap, fmt);
    int len = vsnprintf(buff, sizeof(buff), fmt, ap);
    buff[len] = '\0';
    va_end(ap);

    cout << buff << endl;
}
  • va_start:宏定義,引用最後一個固定參數所以它能夠對可變參數進行定位。
  • va_end:宏定義,函數返回之前一定要調用va_end,這是因為某些實現在函數返回之前需要調整控制信息。
  使用上述函數,我們就可以愉快地打日志了,比如按照如下形式來調用:
log("hi, are you %s?", "luxon28");
log("name=%s, age=%d", "luoxn28", 23);

  更多va_start/va_end信息請點擊:http://www.linuxidc.com/Linux/2016-10/135785.htm 。

定時器的使用

#include <iostream>

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

using namespace std;

// Time callback function
void onTime(int sock, short event, void *arg)
{
    static int cnt = 0;
    cout << "Game Over! " << cnt++ << endl;

    struct timeval tv;
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    if (cnt < 5) {
        // Add timer event
        event_add((struct event *) arg, &tv);
    }
    else {
        cout << "onTime is over" << endl;
    }
}

int main(int argc, char **argv)
{
    cout << event_get_version() << endl;

    struct event_base *base = event_init();
    struct event ev;

    evtimer_set(&ev, onTime, &ev);

    struct timeval timeevent;
    timeevent.tv_sec = 1;
    timeevent.tv_usec = 0;

    event_add(&ev, &timeevent);

    // Start event loop
    event_base_dispatch(base);
    event_base_free(base);

    return 0;
}

輸出結果如下:

  LZ安裝的是Libevent版本是2.0版本,event_init()函數初始化一個事件類結構體,其中已經選擇好了IO復用函數,比如Linux下一般是epoll;初始化了一個事件活動隊列,當事件發生時,會被加入到該事件活動隊列中,然後統一執行事件活動隊列中的所有事件(也就是調用對應的回調函數)。event_base結構體詳細內容如下:

/* 結構體event_base是Libevent的Reactor */
struct event_base {
    /* 初始化Reactor時選擇的一種後端IO復用機制,並記錄在如下字段中 */
    const struct eventop *evsel;
    /* 指向IO復用機制真正存儲的數據,它通過evsel成員的init函數來進行初始化 */
    void *evbase;

    /* 事件變化隊列,其用途是:如果一個文件描述符上注冊的事件被多次修改,則可以使用緩沖區來避免重復的
     * 系統調用(比如epoll_wait)。它僅能用於時間復雜度為O(1)的IO復用技術 */
    struct event_changelist changelist;

    /* 指向信號的後端處理機制,目前僅在singal.h文件中定義了一種處理方法 */
    const struct eventop *evsigsel;
    /* 信號事件處理器使用的數據結構,其中封裝了一個由socketpair創建的管道。它用於信號處理函數和
     * 事件多路分發器之間的通信 */
    struct evsig_info sig;

    /* 以下3個成員是添加到該event_base的虛擬事件、所有事件和激活事件的數量 */
    int virtual_event_count;
    int event_count;
    int event_count_active;

    /* 是否執行完活動事件隊列上的剩余的任務之後就退出事件處理 */
    int event_gotterm;
    /* 是否立即退出事件循環,而不管是否還有任務需要處理 */
    int event_break;
    /* 是否應該啟動一個新的事件循環 */
    int event_continue;

    /* 目前正在處理的活動事件隊列的優先級 */
    int event_running_priority;

    /* 事件循環是否啟動 */
    int running_loop;

    /* 活動事件隊列數組,索引值越小的隊列,優先級越高。高優先級的活動事件隊列中的事件處理器將被優先處理 */
    struct event_list *activequeues;
    /* 活動事件隊列數組的大小,即該event_base共有nactivequeues個不同優先級的活動事件隊列 */
    int nactivequeues;

    /* common timeout logic */

    /* 以下3個成員用於管理通用定時器隊列 */
    struct common_timeout_list **common_timeout_queues;
    int n_common_timeouts;
    int n_common_timeouts_allocated;

    /* 存放延時回調函數的鏈表,事件循環每次成功處理完一個活動事件隊列中的所有事件之後,
     * 就調用一次延遲回調函數 */
    struct deferred_cb_queue defer_queue;

    /* 文件描述符和IO事件之間的映射關系表 */
    struct event_io_map io;

    /* 信號值和信號事件之間的映射關系表 */
    struct event_signal_map sigmap;

    /* 注冊事件隊列,存放IO事件處理器和信號事件處理器 */
    struct event_list eventqueue;

    struct timeval event_tv;

    /* 時間堆 */
    struct min_heap timeheap;
    
    struct timeval tv_cache;

#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
    /** Difference between internal time (maybe from clock_gettime) and
     * gettimeofday. */
    struct timeval tv_clock_diff;
    /** Second in which we last updated tv_clock_diff, in monotonic time. */
    time_t last_updated_clock_diff;
#endif

    /* 多線程支持 */
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
    /* threading support */
    /** The thread currently running the event_loop for this base */
    /* 當前運行該event_base的事件循環的線程 */
    unsigned long th_owner_id;
    /** A lock to prevent conflicting accesses to this event_base */
    void *th_base_lock;            /* 鎖變量 */
    /** The event whose callback is executing right now */
    /* 當前事件循環正在執行哪個事件處理器的回調函數 */
    struct event *current_event;
    /** A condition that gets signalled when we're done processing an
     * event with waiters on it. */
    /* 條件變量,用於喚醒正在等待某個事件處理完畢的線程 */
    void *current_event_cond;
    /** Number of threads blocking on current_event_cond. */
    int current_event_waiters;    /* 等待current_event_cond的線程數 */
#endif

    /** Flags that this base was configured with */
    /* 該vent_base的一些配置參數 */
    enum event_base_config_flag flags;

    /* 下面這些成員變量給工作線程喚醒主線程提供了方法(使用socketpair創建的管道) */
    /* Notify main thread to wake up break, etc. */
    /** True if the base already has a pending notify, and we don't need
     * to add any more. */
    int is_notify_pending;
    /** A socketpair used by some th_notify functions to wake up the main
     * thread. */
    evutil_socket_t th_notify_fd[2];
    /** An event used by some th_notify functions to wake up the main
     * thread. */
    struct event th_notify;
    /** A function used to wake up the main thread from another thread. */
    int (*th_notify_fn)(struct event_base *base);
}

  event_add()函數是往事件結構體中加入監聽的一個事件,這裡是定時事件,當定時事件到時,就會執行對應的回調函數。event_base_dispatch()函數開始執行事件監聽,對應於epoll的話也就是調用epoll_wait了。最後,當程序執行完畢後,需要調用event_base_free()函數來執行資源的銷毀操作,至此,整個定時器事件就執行完畢了。

簡單的HTTP服務器

  使用Libevent,我們可以用不超過50行代碼實現一個簡單的HTTP服務器程序,沒有聽錯,就是幾十行代碼,不像Java那樣,需要配置Tomcat,然後編寫對應的Servet,配置web.xml等等(如果使用SSM或者SSH的話步驟或許更多一點呦 :( )。下面就是一個簡單的HTTP服務器示例代碼:

#include <iostream>

#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>

using namespace std;

#define INFO 1
#define ERR  3
static void log(int level, string info)
{
    switch (level) {
        case INFO:
            cout << "[info] tid[" << pthread_self() << "]: " << info << endl;
            break;
        case ERR:
            cout << "[err] tid[" << pthread_self() << "]: " << info << endl;
            break;
        default:
            break;
    }
}

/**
 * http callback function
 */
void httpHandler(struct evhttp_request *request, void *arg) {
    struct evbuffer *buff = evbuffer_new();
    if (!buff) {
        log(INFO, "evbuffer_new error");
        return;
    }

    evbuffer_add_printf(buff, "Hello world</br>");
    evbuffer_add_printf(buff, "Server Responsed.</br> Requested: %s<br/>", evhttp_request_get_uri(request));
    evbuffer_add_printf(buff, " Host: %s<br/>", evhttp_request_get_host(request));
    evbuffer_add_printf(buff, " Command: %d", evhttp_request_get_command(request));
    evhttp_send_reply(request, HTTP_OK, "OK", buff);
    evbuffer_free(buff);
}

int main(int argc, char **argv)
{
    struct event_base *base = event_base_new();
    struct evhttp *httpServer = evhttp_new(base);

    int result = evhttp_bind_socket(httpServer, NULL, 8080);
    if (result != 0) {
        log(ERR, "evhttp_bind_socket error");
        return -1;
    }

    /* 這是http回調函數 */
    evhttp_set_gencb(httpServer, httpHandler, NULL);
    cout << "Http server start OK..." << endl;

    event_base_dispatch(base);

    evhttp_free(httpServer);
    event_base_free(base);

    return 0;
}

  訪問頁面如下,192.168.1.150主機是linux服務器。

  看到這裡,學習Java Web的小伙伴是不是覺得很熟悉,沒錯,就是像Servlet。LZ個人覺得,對於小型程序來說,使用C/C++的網絡庫編程程序更爽一點,因為更加"接地氣 "一點,也就操作起來更加靈活,使用Java的話肯定要使用Servet容器了,比如Tomcat或者Jboss等,然後各種配置等。但是對於動態Web技術來說,使用Java更爽一點。

Copyright © Linux教程網 All Rights Reserved