一 libevent簡介
libevent是一個支持Windows、linux和bsd等平台的網絡事件驅動程序庫。它支持多種I/O服用機制,按照優先級從高到低依次為:evport、kqueue、epoll、devpoll、rtsig、poll、select。它可根據操作系統,按照優先級從高到底自主選擇驅動。
用戶可以通過http://www.monkey.org/~provos/libevent/來獲取libevent的源碼、libevent出現的背景、以及其他一些詳細資料。
二 libevent的使用
1 初始化事件
我們首先完成對libenvent的事件初始化和事件驅動模型的選擇。(在使用多線程的情況下,一般我們需獲取所返回的事件根基)
main_base = event_init();
event_init函數返回的是一個event_base對象,該對象包括了事件處理過程中的一些全局變量,其結構為:
2 添加事件
在事件初始化完畢後,我們可以使用event_set設置事件,然後使用event_add將其加入。
這裡我們首先完成socket的監聽,然後將其加入的事件隊列中(這裡我們對所有的異常都不做考慮)。
(1)socket監聽
(2)事件設置
socket服務建立後,就可以進行事件設置。我們使用event_set來設置事件對象,其傳入參數包括事件根基(event_base對象),描述符,事件類型,事件發生時的回調函數,回調函數傳入參數。其中事件類型包括EV_READ、EV_WRITE、EV_PERSIST,EV_PERSIST和前兩者結合使用,表示該事件為持續事件。
struct event ev;
event_set(&ev, listen_fd, EV_READ | EV_PERSIST, accept_handle, (void *)&ev);
(3)���件添加與刪除
事件設置好後,就可以將其加入事件隊列。event_add用來將事件加入,它接受兩個參數:要添加的事件和時間的超時值。 如果需要將事件刪除,可以使用event_del來完成。event_del函數會取消所指定的事件。
event_add(&ev, NULL)
3 進入事件循環
事件成功添加後就是萬事具備只欠東風了,libevent提供了多種方式來進入事件循環,我個人常用的是event_dispatch和event_base_loop,前者最後實際是使用當前事件根基來調用event_base_loop。
event_base_loop(main_base, 0);
4 處理連接
到這裡為止,大家已經完成了事件的設置、事件的添加並進入到了事件循環。但是當事件發生時(這裡就是連接建立)如何處理呢? 聰明的用戶會想到前面我們在事件設置時指定的回調函數accept_handle。沒錯,當連接建立時回調函數accept_handle會自動的得到調用。
對於緩沖區的讀寫在非阻塞式網絡編程中是一個難以處理的問題,幸運的是libevent提供了bufferevent和evbuf來替我們完成該項工作。這裡我們采用bufferevent來處理。
(1)生成bufferevent對象
使用bufferevent_new對象來生成bufferevent對象,並分別指定讀、寫、連接錯誤時的處理函數和函數傳入參數。
(2) 設置讀取量
bufferevent的讀事件激活以後,即使用戶沒有讀取完bufferevent緩沖區中的數據, bufferevent讀事件也不會再次被激活。因為bufferevent的讀事件是由其所監控的描述符的讀事件激活的,只有描述符可讀,讀事件才會被激活。可通過設置wm_read.high來控制bufferevent從描述符緩沖區中讀取的數據量。
(3) 將事件加入事件隊列
和前面一樣,在事件設置好後,需將事件加入到事件隊列中, 不過bufferevent的有自己專門的加入函數bufferevent_base_set和激活函數bufferevent_enable。
bufferevent接收兩個參數事件根基個事件對象,前者用來指定事件將加入到哪個事件根基中,後者說明需將那個bufferevnet事件加入。(在多線程的情況下,每個線程可能有自己單獨的事件根基)
在bufferevent初始化完畢後,可以使用bufferevent_enable和bufferevent_disable反復的激活與禁止事件,其接收參數為事件對象和事件標志。其中標志參數為EV_READ和EV_WRITE。
void accept_handle(const int sfd, const short event, void *arg)
5 讀取緩沖區
當緩沖區讀就緒時會自動激活前面注冊的緩沖區讀函數,我們可以使用bufferevent_read函數來讀取緩沖區
bufferevent_read函數參數分別為:所需讀取的事件緩沖區,讀入數據的存放地,希望讀取的字節數。函數返回實際讀取的字節數。
注意:及時緩沖區未讀完,事件也不會再次被激活(除非再次有數據)。因此此處需反復讀取直到全部讀取完畢。
6 寫回客戶端
bufferevent系列函數不但支持讀取緩沖區,而且支持寫緩沖區(即將結果返回給客戶端)。
void buffered_on_read(struct bufferevent *bev, void * arg){
三 結束語
至此我們已經可以使用libevent編寫非阻塞的事件驅動服務器,它支持連接建立、socket可讀等事件的處理。
但在實際的使用事件驅動的服務器中,通常是使用一個線程處理連接,然後使用多個線程來處理請求。後面我將繼續介紹如何使用libevent來編寫多線程的服務器。