很多時候,除了響應事件之外,應用還希望做一定的數據緩衝。比如說,寫入數據的時候,通常的運行模式是:
(1)決定要向連接寫入一些數據,把數據放入到緩衝區中
(2)等待連接可以寫入
(3)寫入儘量多的數據
(4)記住寫入了多少數據,如果還有更多數據要寫入,等待連接再次可以寫入
這種緩衝IO模式很通用,libevent爲此提供了一種通用機制,即bufferevent。bufferevent由一個底層的傳輸端口(如套接字),一個讀取緩衝區和一個寫入緩衝區組成。與通常的事件在底層傳輸端口已經就緒,可以讀取或者寫入的時候執行回調不同的是,bufferevent在讀取或者寫入了足夠量的數據之後調用用戶提供的回調。
3.1 bufferevent和evbuffer
每個bufferevent都有一個輸入緩衝區和一個輸出緩衝區,它們的類型都是“struct evbuffer”。有數據要寫入到bufferevent時,添加數據到輸出緩衝區;bufferevent中有數據供讀取的時候,從輸入緩衝區抽取(drain)數據。
3.2 回調水位
每個bufferevent有兩個數據相關的回調:一個讀取回調和一個寫入回調。默認情況下,從底層傳輸端口讀取了任意量的數據之後會調用讀取回調;輸出緩衝區中足夠量的數據被清空到底層傳輸端口後寫入回調會被調用。通過調整bufferevent的讀取和寫入“水位(watermarks)”可以覆蓋這些函數的默認行爲。
每個bufferevent有四個水位:
(1)讀取低水位:讀取操作使得輸入緩衝區的數據量在此級別或者更高時,讀取回調將被調用。默認值爲0,所以每個讀取操作都會導致讀取回調被調用。
(2)讀取高水位:輸入緩衝區中的數據量達到此級別後,bufferevent將停止讀取,直到輸入緩衝區中足夠量的數據被抽取,使得數據量低於此級別。默認值是無限,所以永遠不會因爲輸入緩衝區的大小而停止讀取。
(3)寫入低水位:寫入操作使得輸出緩衝區的數據量達到或者低於此級別時,寫入回調將被調用。默認值是0,所以只有輸出緩衝區空的時候纔會調用寫入回調。
(4)寫入高水位:bufferevent沒有直接使用這個水位。它在bufferevent用作另外一個bufferevent的底層傳輸端口時有特殊意義。
bufferevent也有“錯誤”或者“事件”回調,用於嚮應用通知非面向數據的事件,如連接已經關閉或者發生錯誤。定義了下列事件標誌:
BEV_EVENT_READING:#讀取操作時發生某事件,具體是哪種事件請看其他標誌。 BEV_EVENT_WRITING:#寫入操作時發生某事件,具體是哪種事件請看其他標誌。 BEV_EVENT_ERROR:#操作時發生錯誤。關於錯誤的更多信息,請調用EVUTIL_SOCKET_ERROR(),獲取錯誤號。 BEV_EVENT_TIMEOUT:#發生超時。 BEV_EVENT_EOF:#遇到文件結束指示。 BEV_EVENT_CONNECTED:#請求的連接過程已經完成。
3.3 延遲迴調
默認情況下,bufferevent的回調在相應的條件發生時立即被執行。(evbuffer的回調也是這樣的)在依賴關係複雜的情況下,這種立即調用會製造麻煩。比如說,假如某個回調在evbuffer A空的時候向其中移入數據,而另一個回調在evbuffer A滿的時候從中取出數據。這些調用都是在棧上發生的,在依賴關係足夠複雜的時候,有棧溢出的風險。
要解決此問題,可以請求bufferevent(或者evbuffer)延遲其回調。條件滿足時,延遲迴調不會立即調用,而是在event_loop()調用中被排隊,然後在通常的事件回調之後執行。
3.4 bufferevent的選項標誌
BEV_OPT_CLOSE_ON_FREE:#釋放bufferevent時關閉底層傳輸端口。這將關閉底層套接字,釋放底層bufferevent等。 BEV_OPT_THREADSAFE:#自動爲bufferevent分配鎖,這樣就可以安全地在多個線程中使用bufferevent。 BEV_OPT_DEFER_CALLBACKS:#設置這個標誌時,bufferevent延遲所有回調,如上所述。 #默認情況下,如果設置bufferevent爲線程安全的,則bufferevent會在調用用戶提供的回調時進行鎖定。 #設置這個選項會讓libevent在執行回調的時候不進行鎖定。 BEV_OPT_UNLOCK_CALLBACKS:
3.5 與基於套接字的bufferevent一起工作
基於套接字的bufferevent是最簡單的,它使用libevent的底層事件機制來檢測底層網絡套接字是否已經就緒,可以進行讀寫操作,並且使用底層網絡調用(如readv、writev、WSASend、WSARecv)來發送和接收數據。
3.5.1 創建基於套接字的bufferevent
可以使用bufferevent_socket_new()創建基於套接字的bufferevent。
struct bufferevent *bufferevent_socket_new(struct event_base *base , evutil_socket_t fd, enum bufferevent_options options);
base是event_base,options是表示bufferevent選項(BEV_OPT_CLOSE_ON_FREE等)的位掩碼,fd是一個可選的表示套接字的文件描述符。如果想以後設置文件描述符,可以設置fd爲-1。
成功時函數返回一個bufferevent,失敗則返回NULL。
3.5.2 在基於套接字的bufferevent上啓動連接
如果bufferevent的套接字還沒有連接上,可以啓動新的連接。
int bufferevent_socket_connect(struct bufferevent *bev , struct sockaddr *address, int addrlen);
address和addrlen參數跟標準調用connect()的參數相同。如果還沒有爲bufferevent設置套接字,調用函數將爲其分配一個新的流套接字,並且設置爲非阻塞的。
如果已經爲bufferevent設置套接字,調用bufferevent_socket_connect()將告知libevent套接字還未連接,直到連接成功之前不應該對其進行讀取或者寫入操作。
連接完成之前可以向輸出緩衝區添加數據。
如果連接成功啓動,函數返回0;如果發生錯誤則返回-1。
注意:如果使用bufferevent_socket_connect()發起連接,將只會收到BEV_EVENT_CONNECTED事件。如果自己調用connect(),則連接上將被報告爲寫入事件。
3.6 通用bufferevent操作
3.6.1 釋放bufferevent
void bufferevent_free(struct bufferevent *bev);
這個函數釋放bufferevent。bufferevent內部具有引用計數,所以,如果釋放bufferevent時還有未決的延遲迴調,則在回調完成之前bufferevent不會被刪除。
如果設置了BEV_OPT_CLOSE_ON_FREE標誌,並且bufferevent有一個套接字或者底層bufferevent作爲其傳輸端口,則釋放bufferevent將關閉這個傳輸端口。
3.6.2 操作回調、水位和啓用/禁用
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx); typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx); void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg); void bufferevent_getcb(struct bufferevent *bufev, bufferevent_data_cb *readcb_ptr, bufferevent_data_cb *writecb_ptr, bufferevent_event_cb *eventcb_ptr, void **cbarg_ptr);
bufferevent_setcb()函數修改bufferevent的一個或者多個回調。readcb、writecb和eventcb函數將分別在已經讀取足夠的數據、已經寫入足夠的數據,或者發生錯誤時被調用。每個回調函數的第一個參數都是發生了事件的bufferevent,最後一個參數都是調用bufferevent_setcb()時用戶提供的cbarg參數:可以通過它向回調傳遞數據。事件回調的events參數是一個表示事件標誌的位掩碼:請看前面的“回調和水位”節。
要禁用回調,傳遞NULL而不是回調函數。
注意:bufferevent的所有回調函數共享單個cbarg,所以修改它將影響所有回調函數。
void bufferevent_enable(struct bufferevent *bufev, short events); void bufferevent_disable(struct bufferevent *bufev, short events); short bufferevent_get_enabled(struct bufferevent *bufev);
可以啓用或者禁用bufferevent上的EV_READ、EV_WRITE或者EV_READ | EV_WRITE事件。沒有啓用讀取或者寫入事件時,bufferevent將不會試圖進行數據讀取或者寫入。
沒有必要在輸出緩衝區空時禁用寫入事件:bufferevent將自動停止寫入,然後在有數據等待寫入時重新開始。
類似地,沒有必要在輸入緩衝區高於高水位時禁用讀取事件:bufferevent將自動停止讀取,然後在有空間用於讀取時重新開始讀取。
默認情況下,新創建的bufferevent的寫入是啓用的,但是讀取沒有啓用。
可以調用bufferevent_get_enabled()確定bufferevent上當前啓用的事件。
void bufferevent_setwatermark(struct bufferevent *bufev, short events, size_t lowmark, size_t highmark);
bufferevent_setwatermark()函數調整單個bufferevent的讀取水位、寫入水位,或者同時調整二者。(如果events參數設置了EV_READ,調整讀取水位。如果events設置了EV_WRITE標誌,調整寫入水位)對於高水位,0表示“無限”。
3.6.3 操作bufferevent中的數據
寫數據
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
這個函數向bufferevent的輸出緩衝區添加數據。bufferevent_write()將內存中從data處開始的size字節數據添加到輸出緩衝區的末尾。成功時這些函數都返回0,發生錯誤時則返回-1。
注意:即使沒有調用bufferevent_enable使能寫事件,調用bufferevent_write時,內部會添加寫事件的監控,觸發寫回調函數後,再把寫事件清除掉,寫回調函數返回時,這個時候檢查一下輸出緩衝區是否低於寫低水位,低了就調用一下寫bufferevent的回調函數。
讀數據
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
這個函數從bufferevent的輸入緩衝區移除數據。bufferevent_read()至多從輸入緩衝區移除size字節的數據,將其存儲到內存中data處。
注意:對於bufferevent_read(),data處的內存塊必須有足夠的空間容納size字節數據。
3.6.4 讀寫超時
跟其他事件一樣,可以要求在一定量的時間已經流逝,而沒有成功寫入或者讀取數據的時候調用一個超時回調。
void bufferevent_set_timeouts(struct bufferevent *bufev, const struct timeval *timeout_read, const struct timeval *timeout_write);
設置超時爲NULL會移除超時回調。
試圖讀取數據的時候,如果至少等待了timeout_read秒,則讀取超時事件將被觸發。試圖寫入數據的時候,如果至少等待了timeout_write秒,則寫入超時事件將被觸發。
注意,只有在讀取或者寫入的時候纔會計算超時。也就是說,如果bufferevent的讀取被禁止,或者輸入緩衝區滿(達到其高水位),則讀取超時被禁止。類似的,如果寫入被禁止,或者沒有數據待寫入,則寫入超時被禁止。
讀取或者寫入超時發生時,相應的讀取或者寫入操作被禁止,然後超時事件回調被調用,帶有標誌BEV_EVENT_TIMEOUT | BEV_EVENT_READING或者BEV_EVENT_TIMEOUT | BEV_EVENT_WRITING。
3.7 類型特定的bufferevent函數
這些bufferevent函數不能支持所有bufferevent類型。
int bufferevent_priority_set(struct bufferevent *bufev, int pri); int bufferevent_get_priority(struct bufferevent *bufev);
這個函數調整bufev的優先級爲pri。關於優先級的更多信息請看event_priority_set()。
成功時函數返回0,失敗時返回-1。這個函數僅能用於基於套接字的bufferevent。
int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd); evutil_socket_t bufferevent_getfd(struct bufferevent *bufev);
這些函數設置或者返回基於fd的事件的文件描述符。只有基於套接字的bufferevent支持setfd()。兩個函數都在失敗時返回-1;setfd()成功時返回0。
struct event_base *bufferevent_get_base(struct bufferevent *bev);
這個函數返回bufferevent的event_base。
3.8 手動鎖定和解鎖
有時候需要確保對bufferevent的一些操作是原子地執行的。爲此,libevent提供了手動鎖定和解鎖bufferevent的函數。
void bufferevent_lock(struct bufferevent *bufev); void bufferevent_unlock(struct bufferevent *bufev);
注意:如果創建bufferevent時沒有指定BEV_OPT_THREADSAFE標誌,或者沒有激活libevent的線程支持,則鎖定操作是沒有效果的。
用這個函數鎖定bufferevent將自動同時鎖定相關聯的evbuffer。這些函數是遞歸的:鎖定已經持有鎖的bufferevent是安全的。當然,對於每次鎖定都必須進行一次解鎖。
3.9 示例代碼
服務器tcp_server.c:
/************************************************************************* # File Name: tcp_server.c # Author: wenong # mail: [email protected] # Created Time: 2016年09月03日 星期六 21時51分08秒 ************************************************************************/ void read_buf_cb(struct bufferevent* bev, void* cbarg) { int ret, i; char buf[MAXBYTES]; ret = bufferevent_read(bev, buf, sizeof(buf)); printf("read_buf_cd length %d\n", ret); for(i = 0; i < ret; i++) { buf[i] = toupper(buf[i]); } bufferevent_write(bev, buf, ret); } void event_cb(struct bufferevent* bev, short event, void* cbarg) { struct event_base* base = (struct event_base*)cbarg; if(BEV_EVENT_READING & event) puts("BEV_EVENT_READING"); if(BEV_EVENT_WRITING & event) puts("BEV_EVENT_WRITING"); if(BEV_EVENT_ERROR & event) puts("BEV_EVENT_ERROR"); if(BEV_EVENT_EOF & event) { puts("BEV_EVENT_EOF"); bufferevent_free(bev); } } void accept_cb(evutil_socket_t serverfd, short what, void * arg) { struct sockaddr_in clientaddr; struct event* ev; struct bufferevent* bev; struct event_base* base = (struct event_base*)arg; int clientaddrlen; int clientfd; puts("Accept client connect\n"); clientaddrlen = sizeof(clientaddr); bzero((void*)&clientaddr, sizeof(clientaddr)); clientfd = accept(serverfd, (struct sockaddr*)&clientaddr, &clientaddrlen); printf("recv clientfd %d\n", clientfd); evutil_make_socket_nonblocking(clientfd); bev = bufferevent_socket_new(base, clientfd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); bufferevent_setcb(bev, (bufferevent_data_cb)read_buf_cb , NULL, (bufferevent_event_cb)event_cb, (void*)base); bufferevent_enable(bev, EV_READ); bufferevent_setwatermark(bev, EV_READ, 10, 0); } 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\n"); 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: [email protected] # Created Time: 2016年09月03日 星期六 22時10分11秒 ************************************************************************/ void* cmd_msg_cb(evutil_socket_t stdinfd, short what, void* arg) { int ret; struct bufferevent* bev = (struct bufferevent*)arg; char buf[MAXBYTES]; puts("get msg from stdin:"); ret = read(stdinfd, buf, sizeof(buf)); bufferevent_write(bev, buf, ret); } void read_buf_cb(struct bufferevent* bev, void* cbarg) { int ret; char buf[MAXBYTES]; ret = bufferevent_read(bev, buf, sizeof(buf)); write(STDOUT_FILENO, buf, ret); } void event_cb(struct bufferevent* bev, short event, void* cbarg) { struct event_base* base = (struct event_base*)cbarg; if(BEV_EVENT_READING & event) puts("BEV_EVENT_READING"); if(BEV_EVENT_WRITING & event) puts("BEV_EVENT_WRITING"); if(BEV_EVENT_ERROR & event) puts("BEV_EVENT_ERROR"); if(BEV_EVENT_EOF & event) { puts("BEV_EVENT_EOF"); event_base_loopexit(base, NULL); //event_base_loopexit(bufferevent_get_base(bev), NULL); } } void main_loop(int clientfd) { struct event_base* base; struct bufferevent* bev; struct event*ev_stdin; base = event_base_new(); bev = bufferevent_socket_new(base, clientfd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); bufferevent_setcb(bev, (bufferevent_data_cb)read_buf_cb , NULL, (bufferevent_event_cb)event_cb, (void*)base); bufferevent_enable(bev, EV_READ); ev_stdin = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST , (event_callback_fn)cmd_msg_cb, (void*)bev); event_add(ev_stdin, NULL); event_base_dispatch(base); bufferevent_free(bev); event_free(ev_stdin); event_base_free(base); puts("exit now..."); } 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; }