libevent源碼分析

1.Libevent有幾個顯著的亮點
        libevent 封裝了底層最高效的網絡模型,windows的compIO,linux下的epoll模型,freebsd的kqueue,提供統一的異步調用接口; 以事件方式驅動,chrome,memcached 都在使用該框架.libevent 同時也支持DNS,HTTP協議和RPC調用框架。libevent老是選擇對應系統框架下最優的多路複用技術(epoll....)實現libevent框架。
*事件驅動(event-driven),高性能;
*輕量級,專一於網絡,不如 ACE 那麼臃腫龐大; 
*跨平臺,支持 Windows、Linux、*BSD和 Mac Os;
*速度:libevent嘗試使用每一個平臺上最高速的非阻塞IO實現,而且不引入太多的額外開銷。
*支持多種 I/O多路複用技術, epoll、poll、dev/poll、select 和kqueue 等;
*支持 I/O,定時器和信號等事件;
*註冊事件優先級;
*能夠統一管理非阻塞文件、定時器和信號事件
2.libevent實現了Reactor模式html

        反應堆模式最重要的思路就是:阻塞+事件+多路分發,事件驅動、同步非阻塞。目前,libevent支持/dev/poll,kqueue(2),select(2),poll(2)和epoll(4)等高併發網絡編程模型。
       Reactor模式是編寫高性能網絡服務器的必備技術之一,它具備以下的優勢:
 1)響應快,不會被單個同步時間所阻塞,雖然 Reactor自己依然是同步的(對於激活事件的處理也須要一個一個的處理,沒有使用多線程技術);
 2)編程相對簡單,能夠最大程度的避免複雜的多線程及同步問題,而且避免了多線程/進程的切換開銷;
 3)可擴展性,能夠方便的經過增長 Reactor實例個數來充分利用 CPU資源(能夠建立多個互不干擾的反應器,用於管理不一樣的文件句柄);
 4)可複用性,reactor框架自己與具體事件處理邏輯無關,具備很高的複用性;
 Reactor模型,必備的4個組件:事件源、Reactor框架、多路複用機制和事件處理程序。
多路複用對應到 libevent 中,依然是 select、poll、epoll 等,可是 libevent 使用結構體 eventop 進行了封裝,以統一的接口來支持這些 I/O多路複用機制,達到了對外隱藏底層系統機制的目的。
3.libevent的方法函數不是線程安全的,由於他們大都操做了未做互斥(加鎖)處理的隊列或鏈表(信號等待隊列、文件和定時器等待隊列、就緒隊列等)資源,因此對libevent使用多線程時,須要一些技巧。好比消息通知機制(隊列+管道)
4.libevent部分接口函數
每個使用libevent的程序,都須要包含<event.h>頭文件,而且須要傳遞-levent標誌給鏈接器linker。
(1)event_init()或者event_base_new()函數執行一次libevent庫的初始化,構造一個libevent實例。
(2)事件通知:對於每個你想監視的文件描述符,必須聲明一個事件結構而且調用event_set()去初始化結構中的成員(好比設置該事件的回調函數)。爲了激活通知,你須要經過調用event_add()將該結構添加到libevent實例監視事件列表。只要是該事件存活,那麼就須要保持該已allocated的事件結構,所以該事件結構須要在堆(heap)上申請。最後,須要調用event_dispatch()函數循環和調度事件。
(3)I/O緩衝區:要實現windows下更高效的IOCP方式的API,IOCP在準備好讀寫事件時不會當即通知你的程序去拷貝數據,而是在數據完成從內核態拷貝到用戶態時才通知應用程序. libevent 2提供了bufferevent 接口,支持這種編程範式 。具體以下:libevent提供了一個按期回調事件頂層的抽象。該抽象被稱爲緩衝事件(buffered event)。緩衝事件提供自動地填充和流掉(drained)的輸入和輸出緩衝區。緩衝時間的用戶再也不須要直接操做I/O,取而待之的是僅僅從輸入緩衝區讀,向輸出緩衝區寫就能夠了。一旦經過bufferevent_new()進行了初始化,bufferevent結構就能夠經過bufferevent_enable()和bufferevent_disable()重複地使用了。做爲替代,對一個套接口的讀寫須要經過調用bufferevent_read()和bufferevent_write()函數來完成。當因爲讀事件而激活bufferevent時,那麼後續將會自動回調讀函數從該文件描述符讀取數據。寫函數將會被回調,不管什麼時候這個輸出緩衝區空間被耗盡到低於寫的下水位(low watemark),一般該值默認爲0。
(4)定時器:libevent經過建立一個定時器來參與到一個通過必定超時時間後的回調事件中。evtimer_set()函數將準備(分配)一個事件結構被用於做爲一個定時器。爲了激活定時器,須要調用evtimer_add()函數(文件描述符調用event_add函數)。相反,須要調用evtimer_del()函數。
(5)超時:除了簡單的定時器,libevent能夠爲文件描述符指定一個超時事件,用於觸發通過一段時間後而沒有被激活的文件描述符執行相應的操做。timeout_set()函數能夠爲一個超時時間初始化一個事件結構。一旦被初始化成功,那麼這個事件必須經過timeout_add()函數激活。爲了取消一個超時事件,能夠調用timeout_del()函數。
(6)異步DNS解析:libevent提供了一個異步DNS解析器,可用於代替標準的DNS解析器。這些函數能夠經過在程序中包含<evdns.h>頭文件而將其導入。在使用任何解析器函數以前,你必須調用evdns_init()函數初始化函數庫。爲轉化一個域名到IP地址,能夠調用evdns_resolve_ipv4()函數。爲了執行一個反向查詢,你能夠調用evdns_resolve_reverse()函數。全部的這些函數,在查找時都會使用回調的方式而避免阻塞的發生。
(7)事件驅動的HTTP服務器:一個簡單的HTTP客戶端/服務器實現。libevent提供了一個簡單的能夠嵌入到你的程序中的並能處理HTTP請求的事件驅動HTTP服務器。爲了使用這種能力,你應該在你的程序中包含<evhttp.h>頭文件。你能夠經過調用evhttp_new()函數來建立一個服務器。經過evhttp_bind_socket()函數添加用於監聽的地址和端口。而後,你能夠註冊一個或多個對到來請求的處理句柄。對於每個URI能夠經過evhttp_set_cb()函數指定一個回調。一般,一個回調函數也能夠經過evhttp_set_gencb()函數完成註冊;若是沒有其餘的回調已經被註冊獲得該URI,那麼這個回調將會與其關聯。
(8)RPC服務器和客戶端框架:libevent提供了一個建立RPC服務器和客戶端的編程框架。它將託管全部的編組和解組的數據結構。
(9) API參考:要瀏覽完整的libevent的API文檔,點擊如下連接。
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/event_8h.html -> event.h libevent主要的頭文件
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evdns_8h.html -> evdns.h 異步DNS解析器
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evhttp_8h.html -> evhttp.h 內置的基於libevent的HTTP服務器
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evrpc_8h.html -> evrpc.h 建立RPC服務器和客戶端的編程框架
struct event_base *event_base_new(void);函數分配而且返回一個新的具備默認設置的event_base(libevent實例、事件管理器實例 )。函數會檢測環境變量,返回一個到event_base的指針.若是發生錯誤,則返回NULL。
struct event_base *event_init(void); 老版本的產生事件管理器實例
void event_set(struct event *ev, int fd, short evtype, void (*cb)(int fd, short evtype, void * arg), void *arg) 初始化一個事件(一個文件事件或定時器事件),指明該事件具體是什麼事件。即設置事件管理的文件句柄、事件類型及回調函數。
ev:執行要初始化的 event 對象; fd:該 event綁定的句柄,對於信號事件,它就是關注的信號;evtype:在該 fd 上關注的事件類型,它能夠是 EV_READ, EV_WRITE, EV_SIGNAL; cb:這是一個函數指針; 因爲定時事件不須要 fd,而且定時事件是根據添加時(event_add)的超時值設定的,所以這裏 event 也不須要設置。事件管理器能夠管理3重類型的事件:entype:(1)I/O事件: EV_WRITE和EV_READ fd=文件描述符(2)定時事件:EV_TIMEOUT fd=-1(3)信號:EV_SIGNAL fd=信號號(4)輔助選項:EV_PERSIST,代表是一個永久事件,該事件不會從等待隊列中取走,永久註冊,不然只監聽一次。entype=EV_READ|EV_PERSIST
事件管理器能夠認爲有3個隊列:信號等待隊列,文件和定時器等待隊列,就緒隊列。
void evtimer_set(struct event *ev, void (*cb)(int, short, void *), NULL,void *arg);只能用來初始化一個定時器事件。
evtimer_set(&ev, timer_cb, NULL); <=>event_set(&ev, -1, 0, timer_cb, NULL); -1就表示這是一個定時器事件,而不是文件句柄。evtimer_set和event_set函數實際上是對event結構體賦值的。
void event_base_set(struct event_base* base, struct event *ev);  指定該事件ev由哪一個事件管理器來管理。
void event_add(struct event *ev, struct timeval *timeout); 將事件添加到ev的事件管理器的事件等待隊列(註冊),其中 timeout 是定時值,只有定時器事件才須要;
void event_base_dispatch(struct event_base *base); 事件管理器循環,開始監控等待隊列中的事件,若是發生,就將該事件從等待隊列中取走,放入就緒事件隊列中,而後按優先級處理(若是要從新監控該事件,須要再次使用event_add將該事件添加到等待事件隊列中)react

5.事件管理器使用的基本步驟
(1)建立一個事件管理器;
(2)建立一個事件,並使用event_set等給事件賦值,指定事件類型回調函數;
(3)給事件指定一個事件管理器,若是不指定,默認爲current指針指向的當前管理器;
(4)將事件添加到管理器事件等待隊列;
(5)運行管理器事件循環。linux

6.實例
(1)定時器
#include <stdio.h>  
#include <event.h>    
void onTime(int sock,short event,void *arg)  
{  
    printf("Game over!\n");  
    struct timeval tv;  
    tv.tv_sec = 1;  
    tv.tv_usec = 0;  
    // 事件執行後,默認就被刪除,須要從新add,使之重複執行  
    event_add((struct event*)arg,&tv);  
}  
int main()  
{  
    event_init();   // 初始化一個libevent實例(事件管理器),指針放在底層的current當前實例中。   
    struct event evTime; //定義一個定時器事件  
    evtimer_set(&evTime,onTime,&evTime); //設置定時器回調函數 
    struct timeval tv;  // 1s後執行  
    tv.tv_sec = 1;  
    tv.tv_usec = 0;           
    event_add(&evTime,&tv); // 添加事件 
    event_dispatch();  // 循環派發事件 
    return 0;

編譯: gcc -o  time_test time_test.c -I/usr/local/libevent/include -L/usr/local/libevent/lib -leventchrome

(2)TCP服務器
#include <stdio.h>  
#include <string.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <netdb.h>  
#include <event.h>  
struct event_base *base;  
// 讀事件  
void onRead(int clifd,short ievent,void *arg)  
{  
    int ilen;  
    char buf[1500];  
    ilen = recv(clifd,buf,1500,0);  
    if(ilen <= 0)  
    {  
        printf("Client close\n");     
        struct event *pread = (struct event*)arg;  
        event_del(pread);  
        delete pread;  
        close(clifd);  
        return;  
    }     
    buf[ilen] = '\0';  
    printf("Accpet: %s\n",buf);  
}  
// 鏈接事件  
void onAccept(int svrfd,short ievent,void *arg)  
{  
    int clifd;  
    struct sockaddr_in cliaddr;  
 
    socklen_t sinsize = sizeof(cliaddr);  
    clifd = accept(svrfd,(struct sockaddr*)&cliaddr,&sinsize);  
 
    struct event *pread = new event;  
    event_set(pread,clifd,EV_READ|EV_PERSIST,onRead,pread);  // 註冊讀(寫)事件  
    event_base_set(base,pread);  
    event_add(pread,NULL);  
}   
int main()  
{  
    int svrfd;  
    struct sockaddr_in svraddr;  
 
    memset(&svrfd,0,sizeof(svraddr));  
    svraddr.sin_family = AF_INET;  
    svraddr.sin_port = htons(1234);  
    svraddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    svrfd = socket(AF_INET,SOCK_STREAM,0);  
    bind(svrfd,(struct sockaddr*)&svraddr,sizeof(svraddr));  
    listen(svrfd,10);  
    // 初始化事件庫  
    base = event_base_new();  
    // 初始化一個鏈接事件,EV_PRESIST指定重複執行該事件  
    struct event evlisten;  
    event_set(&evlisten,svrfd,EV_READ|EV_PERSIST,onAccept,NULL);   
    // 設置爲base事件  
    event_base_set(base,&evlisten);  
    // 添加事件  
    event_add(&evlisten,NULL);  
    // 事件循環  
    event_base_dispatch(base);  
    return 0;  

要實現windows下更高效的IOCP方式的API,IOCP在準備好讀寫事件時不會通知你的程序去拷貝數據,而是在數據完成從內核態拷貝到用戶態時才通知應用程序. libevent 2提供了bufferevent 接口,支持這種編程範式 。編程


(3)HTTP服務器
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <event.h>  
#include <evhttp.h>   
void reqHandler(struct evhttp_request *req,void *arg)  
{  
    struct evbuffer *buf = evbuffer_new();       
    evbuffer_add_printf(buf, "Thanks for the request"); // 發送響應   
    evhttp_send_reply(req,HTTP_OK,"Client",buf);        
    evbuffer_free(buf);         
    return;  
}   
int main(int argc,char **argv)  
{  
    short port = 8000;  
    const char *addr = "192.168.1.11";  
    struct evhttp *httpserv = NULL;  
    event_init();          
    httpserv = evhttp_start(addr,port);  // 啓動http服務  
    evhttp_set_gencb(httpserv, reqHandler,NULL);  // 設置回調  
    printf("Server started on port %d\n",port);  
    event_dispatch();  
    return 0;  
windows

相關文章
相關標籤/搜索