轉自:http://name5566.com/4215.html
?
參考文獻列表:
http://www.wangafu.net/~nickm/libevent-book/
此文編寫的時候,使用到的 Libevent 為 2.0.21
Buffer IO 模式
bufferevent 提供給我們一種 Buffer IO 模式(這里以寫入數據為例):
- 在我們需要通過某個連接發送數據的時候,先將等待發送的數據放入到一個 buffer 中
- 等待此連接可以寫入數據
- 盡可能多的獲取 buffer 中的數據寫入此連接
- 如果 buffer 中還有需要寫入的數據則繼續等待直到此連接可以寫入數據
每一個 bufferevent 都包含了一個輸入 buffer 和一個輸出 buffer,它們的類型為 evbuffer(結構體)。當我們向 bufferevent 寫入數據的時候,實際上數據首先被寫入到了輸出 buffer,當 bufferevent 有數據可讀時,我們實際上是從輸入 buffer 中獲取數據。
目前 bufferevent 目前僅僅支持 stream-oriented 的協議(例如 TCP)并不支持 datagram-oriented 協議(例如 UDP)。一個 bufferevent 的實例負責一個特定的連接上的數據收發。
Libevent 可以按需要創建多種類型的 bufferevent:
- 基于 socket 的 bufferevent。此類型的 bufferevent 使用 socket 來進行數據的收發,使用 event 機制來判斷 socket 是否可以進行讀寫操作
- 異步 IO bufferevent。此類型的 bufferevent 使用 IOCP 接口實現(僅 Windows 下可用且目前處于實驗階段)
- Filtering bufferevent。此類型的 bufferevent 可以在同底層交互時完成一些額外的數據處理工作,例如可以完成數據的壓縮和解析工作。這種類型的 bufferevent 的一個實例會封裝了另外的一個 bufferevent,我們把這個被封裝的 bufferevent 叫做底層 bufferevent
- Paired bufferevent。本文不談
bufferevent 的回調函數
每個 bufferevent 實例可以有 3 個回調函數(通過接口 bufferevent_setcb 設置):
- 讀取回調函數。默認情況下,只要從底層讀取到了數據此回調函數將被調用
- 寫入回調函數。默認情況下,足夠多的數據被寫入底層此回調函數將被調用
- 事件回調函數。當某些事件(錯誤)發生時被調用
對于 buffer 的讀、寫和回調行為可以通過幾個參數來配置,這幾個參數在 Libevent 中被叫做 watermark(水位標記,我們可以將 buffer 想象為一個水池,水位標記用于標記水池中水的多少,也就是說,watermark 用于標記 buffer 中的數據量)。watermark 被實現為整數(類型為 size_t),有幾種類型的 watermark:
- Read low-water mark 用于控制讀取回調函數的行為。當 bufferevent 進行讀取操作時,Read low-water mark 的值決定了輸入 buffer 有多少數據后調用讀取回調函數。默認的情況下,此值為 0,因此 bufferevent 讀取操作都會導致讀取回調函數被調用
- Read high-water mark 用于控制輸入 buffer 的大小。如果輸入 buffer 中的數據量達到 Read high-water mark 的值,那么 bufferevent 將停止讀取。默認的情況下,此值為無限大
- Write low-water mark,用于控制寫入回調函數的行為。當 bufferevent 進行寫入操作時,Write low-water mark 的值決定了輸出 buffer 有多少數據后調用寫入回調函數。默認的情況下,此值為 0,因此寫入回調函數會在輸出 buffer 為空的時候被調用
- Write high-water mark,此值在使用 Filtering bufferevent 有特殊的用途
在一些特殊需求中(詳細并不討論),我們可能需要回調函數被延時執行(這種被延時的回調函數被叫做 Deferred callbacks)。延時回調函數會在事件循環中排隊,并在普通事件回調函數(regular event’s callback)之后被調用。
從基于 socket 的 bufferevent 開始認識 bufferevent
創建基于 socket 的 bufferevent:
- // 創建一個基于 socket 的 bufferevent
- // 函數執行失敗返回 NULL
- struct bufferevent * bufferevent_socket_new (
- struct event_base * base ,
- // socket 文件描述符
- // 此 socket 必須被設置為非阻塞的
- // 可以設置為 -1 表示之后再設置
- evutil_socket_t fd ,
- // bufferevent 的選項
- enum bufferevent_options options );
buffervent 的選項可以用來改變 bufferevent 的行為。可用的選項包括:
-
BEV_OPT_CLOSE_ON_FREE
當 bufferevent 被釋放同時關閉底層(socket 被關閉等) -
BEV_OPT_THREADSAFE
為 bufferevent 自動分配鎖,這樣能夠在多線程環境中安全使用 -
BEV_OPT_DEFER_CALLBACKS
當設置了此標志,bufferevent 會延遲它的所有回調(參考前面說的延時回調) -
BEV_OPT_UNLOCK_CALLBACKS
如果 bufferevent 被設置為線程安全的,用戶提供的回調被調用時 bufferevent 的鎖會被持有
如果設置了此選項,Libevent 將在調用你的回調時釋放 bufferevent 的鎖
建立連接:
- // address 和 addrlen 和標準的 connect 函數的參數沒有區別
- // 如果 bufferevent bev 沒有設置 socket(在創建時可以設置 socket)
- // 那么調用此函數將分配一個新的 socket 給 bev
- // 連接成功返回 0 失敗返回 -1
- int bufferevent_socket_connect ( struct bufferevent * bev ,
- struct sockaddr * address , int addrlen );
簡單的一個范例:
- #include <event2/event.h>
- #include <event2/bufferevent.h>
- #include <sys/socket.h>
- #include <string.h>
- ?
- // 事件回調函數
- void eventcb ( struct bufferevent * bev , short events , void * ptr )
- {
- // 連接成功建立
- if ( events & BEV_EVENT_CONNECTED ) {
- /* We're connected to 127.0.0.1:8080. Ordinarily we'd do
- something here, like start reading or writing. */
- // 出現錯誤
- } else if ( events & BEV_EVENT_ERROR ) {
- /* An error occured while connecting. */
- }
- }
- ?
- int main_loop ( void )
- {
- struct event_base * base ;
- struct bufferevent * bev ;
- struct sockaddr_in sin ;
- ?
- base = event_base_new ();
- ?
- // 初始化連接地址
- memset (& sin , 0 , sizeof ( sin ));
- sin . sin_family = AF_INET ;
- sin . sin_addr . s_addr = htonl ( 0x7f000001 ); /* 127.0.0.1 */
- sin . sin_port = htons ( 8080 ); /* Port 8080 */
- ?
- // 創建一個基于 socket 的 bufferevent
- // 參數 -1 表示并不為此 bufferevent 設置 socket
- bev = bufferevent_socket_new ( base , - 1 , BEV_OPT_CLOSE_ON_FREE );
- ?
- // 為 bufferevent bev 設置回調函數
- // 這里僅僅設置了事件回調函數
- // 后面會詳細談及此函數
- bufferevent_setcb ( bev , NULL , NULL , eventcb , NULL );
- ?
- // 進行連接
- if ( bufferevent_socket_connect ( bev ,
- ( struct sockaddr *)& sin , sizeof ( sin )) < 0 ) {
- /* Error starting connection */
- bufferevent_free ( bev );
- return - 1 ;
- }
- ?
- // 開始事件循環
- event_base_dispatch ( base );
- return 0 ;
- }
更多的 bufferevent API
釋放 bufferevent
- // 如果存在未完成的延時回調,bufferevent 會在回調完成后才被真正釋放
- void bufferevent_free ( struct bufferevent * bev );
bufferevent 回調函數的設置和獲取
- // 讀取、寫入回調函數原型
- // ctx 為用戶自定義數據(由 bufferevent_setcb 設定)
- typedef void (* bufferevent_data_cb )( struct bufferevent * bev , void * ctx );
- ?
- // 事件回調函數原型
- // ctx 為用戶自定義數據(由 bufferevent_setcb 設定)
- // events 選項可以為:
- // BEV_EVENT_READING --- 在 bufferevent 上進行讀取操作時出現了一個事件
- // BEV_EVENT_WRITING --- 在 bufferevent 上進行寫入操作時出現了一個事件
- // BEV_EVENT_ERROR --- 進行 bufferevent 操作時(例如調用 bufferevent API)出錯,獲取詳細的錯誤信息使用 EVUTIL_SOCKET_ERROR()
- // BEV_EVENT_TIMEOUT --- 在 bufferevent 上出現了超時
- // BEV_EVENT_EOF --- 在 bufferevent 上遇到了文件結束符
- // BEV_EVENT_CONNECTED --- 在 bufferevent 上請求連接完成了
- typedef void (* bufferevent_event_cb )( struct bufferevent * bev , short events , void * ctx );
- ?
- // 設置回調函數
- // 如果希望禁用回調函數,那么設置對應的參數為 NULL
- void bufferevent_setcb (
- // bufferevent
- struct bufferevent * bufev ,
- // 讀取回調函數
- bufferevent_data_cb readcb ,
- // 寫入回調函數
- bufferevent_data_cb writecb ,
- // 事件回調函數
- bufferevent_event_cb eventcb ,
- // 用戶定義的數據
- // 這三個回調函數均共享此參數
- void * cbarg
- );
- ?
- // 取回回調函數
- // 參數為 NULL 表示忽略
- void bufferevent_getcb (
- struct bufferevent * bufev ,
- bufferevent_data_cb * readcb_ptr ,
- bufferevent_data_cb * writecb_ptr ,
- bufferevent_event_cb * eventcb_ptr ,
- void ** cbarg_ptr
- );
設置 watermark
- // events 參數可以為
- // EV_READ 表示設置 read watermark
- // EV_WRITE 表示設置 write watermark
- // EV_READ | EV_WRITE 表示設置 read 以及 write watermark
- void bufferevent_setwatermark ( struct bufferevent * bufev , short events ,
- size_t lowmark , size_t highmark );
獲取到輸入和輸出 buffer
- // 獲取到輸入 buffer
- struct evbuffer * bufferevent_get_input ( struct bufferevent * bufev );
- // 獲取到輸出 buffer
- struct evbuffer * bufferevent_get_output ( struct bufferevent * bufev );
添加數據到輸出 buffer
- // 下面兩個函數執行成功返回 0 失敗返回 -1
- ?
- // 向 bufev 的輸出 buffer 中添加大小為 size 的數據
- // 數據的首地址為 data
- int bufferevent_write ( struct bufferevent * bufev ,
- const void * data , size_t size );
- // 向 bufev 的輸出 buffer 中添加數據
- // 數據來源于 buf
- // 此函數會清除 buf 中的所有數據
- int bufferevent_write_buffer ( struct bufferevent * bufev ,
- struct evbuffer * buf );
從輸入 buffer 中獲取數據
- // 從 bufev 的輸入 buffer 中獲取最多 size 字節的數據保存在 data 指向的內存中
- // 此函數返回實際讀取的字節數
- size_t bufferevent_read ( struct bufferevent * bufev , void * data , size_t size );
- // 獲取 bufev 的輸入 buffer 中的所有數據并保存在 buf 中
- // 此函數成功返回 0 失敗返回 -1
- int bufferevent_read_buffer ( struct bufferevent * bufev ,
- struct evbuffer * buf );
關于 bufferevent 的一些高級話題,可以參考: http://www.wangafu.net/~nickm/libevent-book/Ref6a_advanced_bufferevents.html
evbuffers
evbuffer 是一個隊列,在其尾部添加數據和在其頭部刪除數據均被優化了。evbuffer 相關的 API 在這里可以查看: http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
