|
摘要: 譯自http://www.wangafu.net/~nickm/libevent-book/Ref5_evutil.html 轉自:http://blog.sina.com.cn/s/blog_56dee71a0100qo9t.html<event2/util.h>定義了很多在實現可移植應用時有用的函數,libevent內部也使用這些類型和函數。1&... 閱讀全文
摘要: 譯自http://www.wangafu.net/~nickm/libevent-book/Ref4_event.html轉自:http://blog.sina.com.cn/s/blog_56dee71a0100qi0y.htmllibevent的基本操作單元是事件。每個事件代表一組條件的集合,這些條件包括:v 文件描述符已經就緒,可以讀取或者寫入v 文件描述符變為就緒狀態,... 閱讀全文
譯自http://www.wangafu.net/~nickm/libevent-book/Ref3_eventloop.html 轉自:http://blog.sina.com.cn/s/blog_56dee71a0100qe76.html1 運行循環一旦有了一個已經注冊了某些事件的event_base(關于如何創建和注冊事件請看下一節),就需要讓libevent等待事件并且通知事件的發生。 接口
#define EVLOOP_ONCE 0x01 #define EVLOOP_NONBLOCK 0x02 #define EVLOOP_NO_EXIT_ON_EMPTY 0x04
int event_base_loop(struct event_base *base, int flags); 默認情況下,event_base_loop()函數運行event_base直到其中沒有已經注冊的事件為止。執行循環的時候,函數重復地檢查是否有任何已經注冊的事件被觸發(比如說,讀事件的文件描述符已經就緒,可以讀取了;或者超時事件的超時時間即將到達)。如果有事件被觸發,函數標記被觸發的事件為“激活的”,并且執行這些事件。 在flags參數中設置一個或者多個標志就可以改變event_base_loop()的行為。如果設置了EVLOOP_ONCE,循環將等待某些事件成為激活的,執行激活的事件直到沒有更多的事件可以執行,然會返回。如果設置了EVLOOP_NONBLOCK,循環不會等待事件被觸發:循環將僅僅檢測是否有事件已經就緒,可以立即觸發,如果有,則執行事件的回調。 完成工作后,如果正常退出,event_base_loop()返回0;如果因為后端中的某些未處理錯誤而退出,則返回-1。 為幫助理解,這里給出event_base_loop()的算法概要: 偽代碼while (any events are registered with the loop, or EVLOOP_NO_EXIT_ON_EMPTY was set) {
if (EVLOOP_NONBLOCK was set, or any events are already active) If any registered events have triggered, mark them active. else Wait until at least one event has triggered, and mark it active.
for (p = 0; p < n_priorities; ++p { if (any event with priority of p is active) { Run all active events with priority of p. break; /* Do not run any events of a less important priority */ } }
if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set) break; } int event_base_dispatch(struct event_base *base); event_base_dispatch()等同于沒有設置標志的event_base_loop()。所以,event_base_dispatch()將一直運行,直到沒有已經注冊的事件了,或者調用了event_base_loopbreak()或者event_base_loopexit()為止。 這些函數定義在<event2/event.h>中,從libevent 1.0版就存在了。 2 停止循環如果想在移除所有已注冊的事件之前停止活動的事件循環,可以調用兩個稍有不同的函數。 接口int event_base_loopexit(struct event_base *base, const struct timeval *tv); int event_base_loopbreak(struct event_base *base); event_base_loopexit()讓event_base在給定時間之后停止循環。如果tv參數為NULL,event_base會立即停止循環,沒有延時。如果event_base當前正在執行任何激活事件的回調,則回調會繼續運行,直到運行完所有激活事件的回調之才退出。 event_base_loopbreak()讓event_base立即退出循環。它與event_base_loopexit(base,NULL)的不同在于,如果event_base當前正在執行激活事件的回調,它將在執行完當前正在處理的事件后立即退出。 注意event_base_loopexit(base,NULL)和event_base_loopbreak(base)在事件循環沒有運行時的行為不同:前者安排下一次事件循環在下一輪回調完成后立即停止(就好像帶EVLOOP_ONCE標志調用一樣);后者卻僅僅停止當前正在運行的循環,如果事件循環沒有運行,則沒有任何效果。 這兩個函數都在成功時返回0,失敗時返回-1。 示例:立即關閉#include <event2/event.h>
/* Here's a callback function that calls loopbreak */ void cb(int sock, short what, void *arg) { struct event_base *base = arg; event_base_loopbreak(base); }
void main_loop(struct event_base *base, evutil_socket_t watchdog_fd) { struct event *watchdog_event;
/* Construct a new event to trigger whenever there are any bytes to read from a watchdog socket. When that happens, we'll call the cb function, which will make the loop exit immediately without running any other active events at all. */ watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base);
event_add(watchdog_event, NULL);
event_base_dispatch(base); } 示例:執行事件循環10秒,然后退出
#include <event2/event.h>
void run_base_with_ticks(struct event_base *base) { struct timeval ten_sec;
ten_sec.tv_sec = 10; ten_sec.tv_usec = 0;
/* Now we run the event_base for a series of 10-second intervals, printing "Tick" after each. For a much better way to implement a 10-second timer, see the section below about persistent timer events. */ while (1) { /* This schedules an exit ten seconds from now. */ event_base_loopexit(base, &ten_sec);
event_base_dispatch(base); puts("Tick"); } } 有時候需要知道對event_base_dispatch()或者event_base_loop()的調用是正常退出的,還是因為調用event_base_loopexit()或者event_base_break()而退出的。可以調用下述函數來確定是否調用了loopexit或者break函數。 接口int event_base_got_exit(struct event_base *base); int event_base_got_break(struct event_base *base); 這兩個函數分別會在循環是因為調用event_base_loopexit()或者event_base_break()而退出的時候返回true,否則返回false。下次啟動事件循環的時候,這些值會被重設。 這些函數聲明在<event2/event.h>中。event_break_loopexit()函數首次在libevent 1.0c版本中實現;event_break_loopbreak()首次在libevent 1.4.3版本中實現。 3 檢查內部時間緩存有時候需要在事件回調中獲取當前時間的近似視圖,但不想調用gettimeofday()(可能是因為OS將gettimeofday()作為系統調用實現,而你試圖避免系統調用的開銷)。 在回調中,可以請求libevent開始本輪回調時的當前時間視圖。 接口int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv_out); 如果當前正在執行回調,event_base_gettimeofday_cached()函數設置tv_out參數的值為緩存的時間。否則,函數調用evutil_gettimeofday()獲取真正的當前時間。成功時函數返回0,失敗時返回負數。 注意,因為libevent在開始執行回調的時候緩存時間值,所以這個值至少是有一點不精確的。如果回調執行很長時間,這個值將非常不精確。 這個函數是libevent 2.0.4-alpha新引入的。 4 轉儲event_base的狀態接口void event_base_dump_events(struct event_base *base, FILE *f); 為幫助調試程序(或者調試libevent),有時候可能需要加入到event_base的事件及其狀態的完整列表。調用event_base_dump_events()可以將這個列表輸出到指定的文件中。 這個列表是人可讀的,未來版本的libevent將會改變其格式。 這個函數在libevent 2.0.1-alpha版本中引入。 5 廢棄的事件循環函數前面已經討論過,老版本的libevent 具有“當前”event_base的概念。 本文討論的某些事件循環函數具有操作當前event_base的變體。除了沒有base參數外,這些函數跟當前新版本函數的行為相同。 注意2.0版本之前的event_base是不支持鎖的,所以這些函數并不是完全線程安全的:不允許在執行事件循環的線程之外的其他線程中調用_loopbreak()或者_loopexit()函數。
摘要: 譯自http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html轉自:http://blog.sina.com.cn/s/blog_56dee71a0100qdxx.html 使用libevent函數之前需要分配一個或者多個event_base結構體。每個event_base結構體持有一個事件集合,可以檢測以確定哪個事件是... 閱讀全文
摘要: 翻譯自:http://www.wangafu.net/~nickm/libevent-book/Ref1_libsetup.html轉自http://blog.sina.com.cn/s/blog_56dee71a0100q9ks.htmllibevent有一些被整個進程共享的、影響整個庫的全局設置。必須在調用libevent庫的任何其他部分之前修改這些設置,否則,libevent會進入不一致的狀... 閱讀全文
1 從一萬英尺外看LibeventLibevent是用于編寫高速可移植非阻塞IO應用的庫,其設計目標是: v 可移植性:使用libevent編寫的程序應該可以在libevent支持的所有平臺上工作。即使沒有好的方式進行非阻塞IO,libevent也應該支持一般的方式,讓程序可以在受限的環境中運行。 v 速度:libevent嘗試使用每個平臺上最高速的非阻塞IO實現,并且不引入太多的額外開銷。 v 可擴展性:libevent被設計為程序即使需要上萬個活動套接字的時候也可以良好工作。 v 方便:無論何時,最自然的使用libevent編寫程序的方式應該是穩定的、可移植的。 libevent由下列組件構成: v evutil:用于抽象不同平臺網絡實現差異的通用功能。 v event和event_base:libevent的核心,為各種平臺特定的、基于事件的非阻塞IO后端提供抽象API,讓程序可以知道套接字何時已經準備好,可以讀或者寫,并且處理基本的超時功能,檢測OS信號。 v bufferevent:為libevent基于事件的核心提供使用更方便的封裝。除了通知程序套接字已經準備好讀寫之外,還讓程序可以請求緩沖的讀寫操作,可以知道何時IO已經真正發生。(bufferevent接口有多個后端,可以采用系統能夠提供的更快的非阻塞IO方式,如Windows中的IOCP。) v evbuffer:在bufferevent層之下實現了緩沖功能,并且提供了方便有效的訪問函數。 v evhttp:一個簡單的HTTP客戶端/服務器實現。 v evdns:一個簡單的DNS客戶端/服務器實現。 v evrpc:一個簡單的RPC實現。 2 庫創建libevent時,默認安裝下列庫: v libevent_core:所有核心的事件和緩沖功能,包含了所有的event_base、evbuffer、bufferevent和工具函數。 v libevent_extra:定義了程序可能需要,也可能不需要的協議特定功能,包括HTTP、DNS和RPC。 v libevent:這個庫因為歷史原因而存在,它包含libevent_core和libevent_extra的內容。不應該使用這個庫,未來版本的libevent可能去掉這個庫。 某些平臺上可能安裝下列庫: v libevent_pthreads:添加基于pthread可移植線程庫的線程和鎖定實現。它獨立于libevent_core,這樣程序使用libevent時就不需要鏈接到pthread,除非是以多線程方式使用libevent。 v libevent_openssl:這個庫為使用bufferevent和OpenSSL進行加密的通信提供支持。它獨立于libevent_core,這樣程序使用libevent時就不需要鏈接到OpenSSL,除非是進行加密通信。 3 頭文件libevent公用頭文件都安裝在event2目錄中,分為三類: v API頭文件:定義libevent公用接口。這類頭文件沒有特定后綴。 v 兼容頭文件:為已廢棄的函數提供兼容的頭部包含定義。不應該使用這類頭文件,除非是在移植使用較老版本libevent的程序時。 v 結構頭文件:這類頭文件以相對不穩定的布局定義各種結構體。這些結構體中的一些是為了提供快速訪問而暴露;一些是因為歷史原因而暴露。直接依賴這類頭文件中的任何結構體都會破壞程序對其他版本libevent的二進制兼容性,有時候是以非常難以調試的方式出現。這類頭文件具有后綴“_struct.h”。 (還存在不在event2目錄中的較老版本libevent的頭文件,請參考下節:如果需要使用老版本libevent) 4 如果需要使用老版本libeventlibevent 2.0以更合理的、不易出錯的方式修正了API。如果可能,編寫新程序時應該使用libevent 2.0。但是有時候可能需要使用較老的API,例如在升級已存的應用時,或者支持因為某些原因不能安裝2.0或者更新版本libevent的環境時。 較老版本的libevent頭文件較少,也不安裝在event2目錄中。

在2.0以及以后版本的libevent中,老的頭文件仍然會作為新頭文件的封裝而存在。 其他關于使用較老版本的提示: v 1.4版之前只有一個庫libevent,它包含現在分散到libevent_core和libevent_extra中的所有功能。 v 2.0版之前不支持鎖定:只有確定不同時在多個線程中使用同一個結構體時,libevent才是線程安全的。 下面的節還將討論特定代碼區域可能遇到的已經廢棄的API。 5 關于版本狀態的提示1.4.7及以前版本應該被認為是完全廢棄的。1.3之前的版本應該被認為是充滿bug的。 (此外,不要向libevent維護者發送任何關于1.4.x或者更早版本的新特征,這些版本被認為是穩定的發布版本。如果在1.3x或者更早版本中發現bug,在報告之前請確定在最新的穩定發布版本中問題仍然存在:后續發布可能已經解決了問題。)
摘要: 原文出處:http://www.wangafu.net/~nickm/libevent-book/01_intro.html轉自: http://blog.sina.com.cn/s/blog_56dee71a0100q2i9.html大多數程序員從阻塞IO調用開始學習。如果調用在操作完成之前,或者足夠的時間已經流逝使得網絡棧放棄操作之前,不會返回,那么就是異步的。比如說,在TCP連接上... 閱讀全文
前言 可以說對于任何網絡庫(模塊)而言,一個緩沖模塊都是必不可少的。緩沖模塊主要用于緩沖從網絡接收到的數據,以及 用戶提交的數據(用于發送)。很多時候,我們還需要將網絡模塊層(非TCP層)的這些緩沖數據拷貝到用戶層,而這些內存拷貝 都會消耗時間。 在這里,我簡要分析下libevent的相關代碼(event.h和buffer.c)。 結構 關于libevent的緩沖模塊,主要就是圍繞evbuffer結構體展開。先看下evbuffer的定義:
struct evbuffer{ // 當前有效緩沖區的內存起始地址 u_char *buffer; // 整個分配(realloc)用來緩沖的內存起始地址 u_char *orig_buffer; // origin_buffer和buffer之間的字節數 size_t misalign; // 整個分配用來緩沖的內存字節數 size_t totallen; // 當前有效緩沖區的長度(字節數) size_t off; //回到函數,當緩沖區有變化的時候會被調用 void (*cb)(struct evbuffer *, size_t, size_t, void *); //回調函數的參數 void *cbarg; }; libevent的緩沖是一個連續的內存區域,其處理數據的方式(寫數據和讀數據)更像一個隊列操作方式:從后寫入,從前 讀出。evbuffer分別設置相關指針(一個指標)用于指示讀出位置和寫入位置。其大致結構如圖: 
orig_buffer指向由realloc分配的連續內存區域,buffer指向有效數據的內存區域,totallen表示orig_buffer指向的內存 區域的大小,misalign表示buffer相對于orig_buffer的偏移,off表示有效數據的長度。
實際運作 這里我將結合具體的代碼分析libevent是如何操作上面那個隊列式的evbuffer的,先看一些輔助函數:
void evbuffer_drain(struct evbuffer *buf, size_t len) 該函數主要操作一些指標,當每次從evbuffer里讀取數據時,libevent便會將buffer指針后移,同時增大misalign,減小off, 而該函數正是做這件事的。說白了,該函數就是用于調整緩沖隊列的前向指標。
int evbuffer_expand(struct evbuffer *buf, size_t datlen) 該函數用于擴充evbuffer的容量。每次向evbuffer寫數據時,都是將數據寫到buffer+off后,buffer到buffer+off之間已被 使用,保存的是有效數據,而orig_buffer和buffer之間則是因為讀取數據移動指標而形成的無效區域。 evbuffer_expand的擴充策略在于,首先判斷如果讓出orig_buffer和buffer之間的空閑區域是否可以容納添加的數據,如果 可以,則移動buffer和buffer+off之間的數據到orig_buffer和orig_buffer+off之間(有可能發生內存重疊,所以這里移動調用的 是memmove),然后把新的數據拷貝到orig_buffer+off之后;如果不可以容納,那么重新分配更大的空間(realloc),同樣會移動 數據。 擴充內存的策略為:確保新的內存區域最小尺寸為256,且以乘以2的方式逐步擴大(256、512、1024、...)。 了解了以上兩個函數,看其他函數就比較簡單了。可以看看具體的讀數據和寫數據:
int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen) 該函數用于添加一段用戶數據到evbuffer中。很簡單,就是先判斷是否有足夠的空閑內存,如果沒有則調用evbuffer_expand 擴充之,然后直接memcpy,更新off指標。
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen) 該函數用于將evbuffer中的數據復制給用戶空間(讀數據)。簡單地將數據memcpy,然后調用evbuffer_drain移動相關指標。
struct evbuffer* evbuffer_new(void) 動態分配一個struct evbuffer結構,需要調用evbuffer_free釋放內存。
void evbuffer_free(struct evbuffer *buffer) 釋放buffer所占用的內存。 int evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) 移動數據從一個evbuffer到另一個evbuffer。 實際上還是調用了evbuffer_add添加數據到outbuf中。但會清除inbuf中的數據。 返回值:成功返回0, 失敗返回-1。 int evbuffer_add_printf( struct evbuffer *, const char* fmt, ) 添加一個格式化的字符串到evbuffer尾部。 u_char *evbuffer_find(struct evbuffer *buffer, const u_char *what, size_t len) 查找緩沖區中是否存在指定的字符串what。 注意這里使用的是u_char類型,說明有可能查找的數據不是以’\0’結尾 如果存在返回指向字符串what的指針,沒有則返回NULL。 int evbuffer_read(struct evbuffer *buf, int fd, int howmuch) 調用read/recv函數,從文件描述符fd上讀取數據到evbuffer中。如果緩沖區不夠,調用evbuffer_expand擴充緩沖區。 int evbuffer_write(struct evbuffer *buffer, int fd) 把緩沖區中的數據,調用send/write函數寫入文件描述符fd上, 如果send/write函數寫入的字節數大于0,則調用evbuffer_drain刪除已寫的數據。 char *evbuffer_readline(struct evbuffer *buffer) 讀取數據以"\r\n","\n\r", "\r" 或者 "\n"結尾。 返回動態分配內存,需要調用者自己使用free來釋放內存。返回一個以“\0”結尾的字符串。 void evbuffer_setcb(struct evbuffer *buffer, void (*cb)(struct evbuffer *, size_t, size_t, void *), void *cbarg) 設置回調函數。當緩沖區中發生變化時, 調用設置的回調函數。 Evbuffer提供的API已經全部介紹完畢,接下來我們通過一個實例進一步學習如何使用evbuffer, 想要使用evbuffer,系統里必須已經安裝了libevent。 例子代碼如下:evbuffer-test.c #include <stdio.h> #include <string.h> #include <assert.h>
//引入libevent頭文件 #include "event.h"
int main(int argc, char** argv) { struct evbuffer* buff = NULL; char c, c2[3] = {0}; buff = evbuffer_new(); assert(buff != NULL); evbuffer_add(buff, "1", 1); evbuffer_add(buff, "2", 1); evbuffer_add(buff, "3", 1); evbuffer_add_printf(buff, "%d%d", 4, 5); assert(buff->off == 5);
evbuffer_remove(buff, &c, sizeof(char)); assert(c == '1'); evbuffer_remove(buff, &c, sizeof(char)); assert(c == '2'); evbuffer_remove(buff, &c, sizeof(char)); assert(c == '3'); evbuffer_remove(buff, c2, 2); assert(strcmp(c2, "45") == 0); assert(buff->off == 0); evbuffer_add(buff, "test\r\n", 6); assert(buff->off == 6); char* line = evbuffer_readline(buff); assert(strcmp(line, "test") ==0); assert(buff->off == 0); free(line); evbuffer_free(buff); printf("ok\n"); return 0; }
摘要: 回顧: 線程與進程 1.進程由于資源獨立,進程的主要關注是解決資源共享 2.線程先天是數據共享,線程主要關注共享數據臟 1.互斥量(線程內) 讀寫鎖 2.信號/條件量調度... 閱讀全文
在Linux的多線程中使用信號機制,與在進程中使用信號機制有著根本的區別,可以說是完全不同。在進程環境中,對信號的處理是,先注冊信號處理函數,當信號異步發生時,調用處理函數來處理信號。它完全是異步的(我們完全不知到信號會在進程的那個執行點到來!)。然而信號處理函數的實現,有著許多的限制;比如有一些函數不能在信號處理函數中調用;再比如一些函數read、recv等調用時會被異步的信號給中斷(interrupt),因此我們必須對在這些函數在調用時因為信號而中斷的情況進行處理(判斷函數返回時 enno 是否等于 EINTR)。
但是在多線程中處理信號的原則卻完全不同,它的基本原則是:將對信號的異步處理,轉換成同步處理,也就是說用一個線程專門的來“同步等待”信號的到來,而其它的線程可以完全不被該信號中斷/打斷(interrupt)。這樣就在相當程度上簡化了在多線程環境中對信號的處理。而且可以保證其它的線程不受信號的影響。這樣我們對信號就可以完全預測,因為它不再是異步的,而是同步的(我們完全知道信號會在哪個線程中的哪個執行點到來而被處理!)。而同步的編程模式總是比異步的編程模式簡單。其實多線程相比于多進程的其中一個優點就是:多線程可以將進程中異步的東西轉換成同步的來處理。
1. sigwait函數:
sigwait等一個或者多個指定信號發生。 它所做的工作只有兩個:第一,監聽被阻塞的信號;第二,如果所監聽的信號產生了,則將其從未決隊列中移出來(這里實時信號和非實時信號又有區別,體現在取出的順序等,具體自己取網上查,這里不再詳述)。sigwait并不改變信號掩碼的阻塞與非阻塞狀態。 在POSIX標準中,當進程收到信號時,如果是多線程的情況,我們是無法確定是哪一個線程處理這個信號。而sigwait是從進程中pending的信號中,取走指定的信號。這樣的話,如果要確保sigwait這個線程收到該信號,那么所有線程含主線程以及這個sigwait線程則必須block住這個信號,因為如果自己不阻塞就沒有未決狀態(阻塞狀態)信號,別的所有線程不阻塞就有可能當信號過來時,被其他的線程處理掉。
記住: 在多線程代碼中,總是使用sigwait或者sigwaitinfo或者sigtimedwait等函數來處理信號。 而不是signal或者sigaction等函數。因為在一個線程中調用signal或者sigaction等函數會改變所以線程中的 信號處理函數。而不是僅僅改變調用signal/sigaction的那個線程的信號處理函數。
2. pthread_sigmask函數: 每個線程均有自己的信號屏蔽集(信號掩碼),可以使用pthread_sigmask函數來屏蔽某個線程對某些信號的 響應處理,僅留下需要處理該信號的線程來處理指定的信號。實現方式是:利用線程信號屏蔽集的繼承關系 (在主進程中對sigmask進行設置后,主進程創建出來的線程將繼承主進程的掩碼)
3. pthread_kill函數: 在多線程程序中,一個線程可以使用pthread_kill對同一個進程中指定的線程(包括自己)發送信號。注意在多線程中 一般不使用kill函數發送信號,因為kill是對進程發送信號,結果是:正在運行的線程會處理該信號,如果該線程沒有 注冊信號處理函數,那么會導致整個進程退出。 記住:調用sigwait同步等待的信號必須在調用線程中被屏蔽,并且通常應該在所有的線程中被屏蔽(這樣可以保證信號絕不會被送到除了調用sigwait的任何其它線程),這是通過利用信號掩碼的繼承關系來達到的。
|