libevent原始碼詳解(四)應用流程詳解
libevent應用流程
- 1.呼叫event_base_new()建立自己的event_base。
- 2.呼叫event_new()建立自己的事件。
- 3.呼叫event_add將自己建立的event新增到event_base中。
- 4.呼叫event_base_dispatch()進行迴圈等待事件發生。
其它的一些介面evsignal_add(),evtimer_add,evbuffer_setcb都是在event_add的基礎上進行封裝。應用的流程原理本上面沒什麼區別。
我們隨便看一個例子,然後按照應用流程來解析幾個關鍵介面的實現。
int tcp_connect_server(const char* server_ip, int port);
void cmd_msg_cb(int fd, short events, void* arg);
void socket_read_cb(int fd, short events, void *arg);
int main(int argc, char** argv)
{
if( argc < 3 )
{
printf("please input 2 parameter\n");
return -1;
}
//兩個引數依次是伺服器端的IP地址、埠號
int sockfd = tcp_connect_server(argv[1], atoi(argv[2]));
if( sockfd == -1)
{
perror("tcp_connect error ");
return -1;
}
printf("connect to server successful\n");
struct event_base* base = event_base_new();
struct event *ev_sockfd = event_new(base , sockfd, EV_READ | EV_PERSIST, socket_read_cb, NULL);
event_add(ev_sockfd, NULL);
//監聽終端輸入事件
struct event* ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb,(void*)&sockfd);
event_add(ev_cmd, NULL);
event_base_dispatch(base);
printf("finished \n");
return 0;
}
event_base_new解析
event_base_new實現如下
struct event_base *
event_base_new(void)
{
struct event_base *base = NULL;
struct event_config *cfg = event_config_new();
if (cfg) {
base = event_base_new_with_config(cfg);
event_config_free(cfg);
}
return base;
}
我們呼叫event_base_new建立的event_base是利用event_config設定的。我們看看event_config的結構,然後分析event_config_new()返回的event_config都有些什麼性質。
event_config解析
event_config實現如下
struct event_config {
TAILQ_HEAD(event_configq, event_config_entry) entries;//尾佇列
int n_cpus_hint;
enum event_method_feature require_features;
enum event_base_config_flag flags;
};
event_config_new()實現如下
struct event_config *
event_config_new(void)
{
struct event_config *cfg = mm_calloc(1, sizeof(*cfg));
if (cfg == NULL)
return (NULL);
TAILQ_INIT(&cfg->entries);
return (cfg);
}
可以看到,event_config_new返回來的event_config,尾佇列只有佇列頭沒有entry,其它三個成員的值也都是0。基本沒什麼卵用。
event_config的一些操作
- event_config_set_flag(struct event_config *cfg, int flag) //設定flags
- event_config_avoid_method(struct event_config *cfg, const char *method)//設定entries
- event_config_require_features(struct event_config *cfg,int features)//設定features
- event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)//設定n_cpus_hint
這四個函式分別設定了event_config的四個資料成員。如果我們在呼叫event_config_new後沒有呼叫這四個函式設定返回的event_config,那麼返回來的event_config就是空的,沒什麼卵用。
event_base_new_with_config解析
event_base_new_with_config實現如下.基本上就是初始化event_base的資料成員。
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
int i;
struct event_base *base;
int should_check_environment;
#ifndef _EVENT_DISABLE_DEBUG_MODE
event_debug_mode_too_late = 1;
#endif
if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
event_warn("%s: calloc", __func__);
return NULL;
}
detect_monotonic();
gettime(base, &base->event_tv);
//建立最小堆
min_heap_ctor(&base->timeheap);
//普通事件佇列建立
TAILQ_INIT(&base->eventqueue);
base->sig.ev_signal_pair[0] = -1;
base->sig.ev_signal_pair[1] = -1;
base->th_notify_fd[0] = -1;
base->th_notify_fd[1] = -1;
event_deferred_cb_queue_init(&base->defer_queue);
base->defer_queue.notify_fn = notify_base_cbq_callback;
base->defer_queue.notify_arg = base;
//呼叫event_config_new返回來的預設cfg的flags為0
if (cfg)
base->flags = cfg->flags;
evmap_io_initmap(&base->io);//io對映
evmap_signal_initmap(&base->sigmap);//signal對映。signal和io的對映看之前event_base的分析文章
event_changelist_init(&base->changelist);
base->evbase = NULL;
should_check_environment =
!(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));
/*
eventops是一個全域性陣列,裡面儲存了後端各種multiplexer的封裝。
各個平臺支援的multiplexer方法libevent會預先封裝好,然後在編譯時檢測平臺支援的方法,然後生成對應的巨集來設定eventops的值。
初始化的時候只需要從裡面讀出對應的handle然後賦值給event base中的成員函式base,
base就可以呼叫到multiplexer中的方法。
libevent強制每個multiplexer都要實現init,add,del,dispatch,dealloc五個介面。
*/
for (i = 0; eventops[i] && !base->evbase; i++) {
if (cfg != NULL) {
/*
event_config_is_avoided_method會遍歷cfg的尾佇列nentries。
預設情況下,呼叫event_config_new返回來的預設cfg的nentries為空,
所以這個函式總是返回false
*/
if (event_config_is_avoided_method(cfg,
eventops[i]->name))
continue;
if ((eventops[i]->features & cfg->require_features)
!= cfg->require_features)
continue;
}
/* also obey the environment variables */
if (should_check_environment &&
event_is_method_disabled(eventops[i]->name))
continue;
base->evsel = eventops[i];
//初始化後端
base->evbase = base->evsel->init(base);
}
if (base->evbase == NULL) {
event_warnx("%s: no event mechanism available",
__func__);
base->evsel = NULL;
event_base_free(base);
return NULL;
}
if (evutil_getenv("EVENT_SHOW_METHOD"))
event_msgx("libevent using: %s", base->evsel->name);
/*
設定event_base的優先順序,並初始化event_base中的就緒佇列。
就緒佇列的個數跟初始化時的優先順序有關係。
預設情況下,event_base只有一個就緒活動佇列。
*/
if (event_base_priority_init(base, 1) < 0) {
event_base_free(base);
return NULL;
}
/* 多執行緒設定 */
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
if (EVTHREAD_LOCKING_ENABLED() &&
(!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
int r;
EVTHREAD_ALLOC_LOCK(base->th_base_lock,
EVTHREAD_LOCKTYPE_RECURSIVE);
base->defer_queue.lock = base->th_base_lock;
EVTHREAD_ALLOC_COND(base->current_event_cond);
r = evthread_make_base_notifiable(base);
if (r<0) {
event_warnx("%s: Unable to make base notifiable.", __func__);
event_base_free(base);
return NULL;
}
}
#endif
return (base);
}
event_new解析
event_new()的作用就是設定事件的型別、回撥函式、回撥函式引數、繫結的IO事件fd,並設定事件的初始狀態為EVLIST_INIT,然後返回一個新建立的事件。
具體的實現程式碼如下
struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
{
struct event *ev;
ev = mm_malloc(sizeof(struct event));
if (ev == NULL)
return (NULL);
if (event_assign(ev, base, fd, events, cb, arg) < 0) {
mm_free(ev);
return (NULL);
}
return (ev);
}
int
event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
{
if (!base)
base = current_base;
_event_debug_assert_not_added(ev);
ev->ev_base = base;
ev->ev_callback = callback;//設定回撥函式
ev->ev_arg = arg;//設定回撥函式引數
ev->ev_fd = fd;
ev->ev_events = events;//設定事件型別
ev->ev_res = 0;
ev->ev_flags = EVLIST_INIT;//設定事件初始狀態
ev->ev_ncalls = 0;
ev->ev_pncalls = NULL;
if (events & EV_SIGNAL) {
if ((events & (EV_READ|EV_WRITE)) != 0) {
event_warnx("%s: EV_SIGNAL is not compatible with "
"EV_READ or EV_WRITE", __func__);
return -1;
}
ev->ev_closure = EV_CLOSURE_SIGNAL;
} else {
if (events & EV_PERSIST) {
evutil_timerclear(&ev->ev_io_timeout);
ev->ev_closure = EV_CLOSURE_PERSIST;
} else {
ev->ev_closure = EV_CLOSURE_NONE;
}
}
min_heap_elem_init(ev);
if (base != NULL) {
/* by default, we put new events into the middle priority */
ev->ev_pri = base->nactivequeues / 2;
}
_event_debug_note_setup(ev);
return 0;
}
event_add解析
先上原始碼。event_add主要是封裝了event_add_internal。
event_add_internal的主要實現如下。略掉了一些檢查程式碼。
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
int tv_is_absolute)
{
struct event_base *base = ev->ev_base;
int res = 0;
int notify = 0;
event_queue_insert
...
/*
IO事件和signal事件會執行這個if裡面的程式碼。timeout事件不執行。
主要就是將訊號值和IO的fd值對映到一個數組中。
需要對映的原因是libevent支援將一個fd或者訊號值同時響應多個回撥函式。具體請看以前章節的分析。
對映完畢後,呼叫event_queue_insert將事件插入到event_base的普通事件佇列eventqueue中。並將其狀態設定為EVLIST_INSERTED
*/
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
if (ev->ev_events & (EV_READ|EV_WRITE))
res = evmap_io_add(base, ev->ev_fd, ev);
else if (ev->ev_events & EV_SIGNAL)
res = evmap_signal_add(base, (int)ev->ev_fd, ev);
if (res != -1)
event_queue_insert(base, ev, EVLIST_INSERTED);
if (res == 1) {
/* evmap says we need to notify the main thread. */
notify = 1;
res = 0;
}
}
//timeout事件執行這個if裡面的程式碼。IO事件和signal事件不執行。
if (res != -1 && tv != NULL) {
struct timeval now;
int common_timeout;
if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
ev->ev_io_timeout = *tv;
//如果事件已經在最小堆中,則將其從堆中刪除
if (ev->ev_flags & EVLIST_TIMEOUT) {
/* XXX I believe this is needless. */
if (min_heap_elt_is_top(ev))
notify = 1;
event_queue_remove(base, ev, EVLIST_TIMEOUT);
}
/*如果這個timeout事件之前已經發生,然後又在自己的回撥函式中呼叫event_add,那麼將這個事件從就緒佇列中刪除*/
if ((ev->ev_flags & EVLIST_ACTIVE) &&
(ev->ev_res & EV_TIMEOUT)) {
if (ev->ev_events & EV_SIGNAL) {
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
}
event_queue_remove(base, ev, EVLIST_ACTIVE);
}
gettime(base, &now);
common_timeout = is_common_timeout(tv, base);
if (tv_is_absolute) {
ev->ev_timeout = *tv;
} else if (common_timeout) {
struct timeval tmp = *tv;
tmp.tv_usec &= MICROSECONDS_MASK;
evutil_timeradd(&now, &tmp, &ev->ev_timeout);
ev->ev_timeout.tv_usec |=
(tv->tv_usec & ~MICROSECONDS_MASK);
} else {
evutil_timeradd(&now, tv, &ev->ev_timeout);
}
event_debug((
"event_add: timeout in %d seconds, call %p",
(int)tv->tv_sec, ev->ev_callback));
//將事件加入最小堆
event_queue_insert(base, ev, EVLIST_TIMEOUT);
if (common_timeout) {
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
if (ev == TAILQ_FIRST(&ctl->events)) {
common_timeout_schedule(ctl, &now, ev);
}
} else {
//如果我們加入最小堆的事件處在堆的頭部,則提早喚醒主執行緒
if (min_heap_elt_is_top(ev))
notify = 1;
}
}
//喚醒主執行緒
if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
evthread_notify_base(base);
}
Q1: timeout事件可以用min-head最小堆來儲存,也可以用event_base中的型別為struct common_timeout_list 的common_timeout_queues陣列來儲存。那麼什麼時候該使用最小堆,什麼時候使用common_timeout_queues佇列呢?
A: 當一個timeout同時要響應上千個事件,同時呼叫上千個回撥函式時,使用common_timeout_queues陣列比最小堆高效得多。
Q2: 如何使用?
A: 建立timeout事件後,要呼叫event_base_init_common_timeout介面,然後再呼叫event_add
event_base_dispatch解析
event_base_dispatch主要是包裝了event_base_loop函式。幾個關鍵的函式做了註釋。
int
event_base_dispatch(struct event_base *event_base)
{
return (event_base_loop(event_base, 0));
}
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
struct timeval tv;
struct timeval *tv_p;
int res, done, retval = 0;
/* Grab the lock. We will release it inside evsel.dispatch, and again
* as we invoke user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->running_loop) {
event_warnx("%s: reentrant invocation. Only one event_base_loop"
" can run on each event_base at once.", __func__);
EVBASE_RELEASE_LOCK(base, th_base_lock);
return -1;
}
base->running_loop = 1;
clear_time_cache(base);
if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
evsig_set_base(base);
done = 0;
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
base->th_owner_id = EVTHREAD_GET_ID();
#endif
base->event_gotterm = base->event_break = 0;
while (!done) {
base->event_continue = 0;
/* Terminate the loop if we have been asked to */
if (base->event_gotterm) {
break;
}
if (base->event_break) {
break;
}
//檢查系統事件是否被人為往前設定,如果是,則將最小堆和common_timeout_queues中的時間戳減去相應的值
timeout_correct(base, &tv);
tv_p = &tv;
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);
}
//event_base中沒有普通等待事件和就緒事件,則直接結束迴圈
if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
event_debug(("%s: no events registered.", __func__));
retval = 1;
goto done;
}
/* update last old time */
gettime(base, &base->event_tv);
clear_time_cache(base);
res = evsel->dispatch(base, tv_p);
if (res == -1) {
event_debug(("%s: dispatch returned unsuccessfully.",
__func__));
retval = -1;
goto done;
}
update_time_cache(base);
//處理要就緒的timeout事件
timeout_process(base);
if (N_ACTIVE_CALLBACKS(base)) {
//處理就緒的IO事件和signal事件
int n = event_process_active(base);
if ((flags & EVLOOP_ONCE)
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
event_debug(("%s: asked to terminate loop.", __func__));
done:
clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);
}
相關推薦
libevent原始碼詳解(四)應用流程詳解
libevent應用流程 1.呼叫event_base_new()建立自己的event_base。 2.呼叫event_new()建立自己的事件。 3.呼叫event_add將自己建立的event新增到event_base中。 4.呼叫event_base_
蘋果App Store上傳應用流程詳解
AppStore上傳及更新文件 必要條件 上傳AppStore所需的賬號密碼 上傳準備 1.bundle identifier 對應上傳AppStore證書所使用的bundleID填寫 2.版本號version 如3.1.0,3在版本大規模改動時進行調整,1是在版本有新特色及較大改動時跳轉,0是
高通sensor架構例項分析之三(adsp上報資料詳解、校準流程詳解)
本系列導航: 從adsp獲取資料的方法分為同步、非同步兩種方式,但一般在實際使用中使用非同步方式,因為同步獲取資料會因外設匯流排速率低的問題阻塞smgr,降低效率,增加功耗。 Sensor上報資料的方式分為如下幾種 sync 同步資料上報,(每次上報一個數據) async
Libevent原始碼分析(四)--- libevent事件機制
之前幾個章節都是分析libevent的輔助功能,這一節將要詳細分析libevent處理事件的流程和機制,在分析之前先看一下libevent的使用方法,本文也將以libevent的使用方式入手來分析libevent的工作機制。 void cb_func(ev
libevent原始碼分析(四)
還是上次那個訊號函式的例子, sample/signal-test.c——註冊訊號以及回撥事件。 /* Initalize one event */ event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST,
Android應用Context詳解及原始碼解析
轉自 http://blog.csdn.net/yanbober/article/details/45967639 1 背景 今天突然想起之前在上家公司(做TV與BOX盒子)時有好幾個人問過我關於Android的Context到底是啥的問題,所以就馬上
Android應用ViewDragHelper詳解及部分原始碼淺析
1 背景 很久沒有更新部落格了,忙裡偷閒產出一篇。寫這片文章主要是去年專案中的一個需求,當時三下五除二的將其實現了,但是原始碼的閱讀卻一直扔在那遲遲沒有時間理會,現在揀起來看看吧,否則心裡一直不踏實。 關於啥是ViewDragHelper,這裡不再
死磕Netty原始碼之記憶體分配詳解(四)PoolArena全域性記憶體分配
記憶體分配 全域性分配 記憶體池的初始階段執行緒是沒有記憶體快取的,所以最開始的記憶體分配都需要在全域性分配區進行分配 全域性分配區的記憶體構造和執行緒私有分配區的類似(包含Tiny、Small、Normal幾種規模 計算索引的方式也都是一模一樣的
企業如何應用ERP?企業應用ERP流程詳解。
ERP系統是企業資源計劃(EnterpriseResource Planning )的簡稱,是指建立在資訊科技基礎上,集資訊科技與先進管理思想於一身,以系統化的管理思想,為企業員工及決策層提供決策手段的管理平臺。因此企業應用ERP是一個整體性,流程性的過程,在這裡給大家介紹ERP應用的常
js陣列中的find、filter、forEach、map四個方法的詳解和應用例項
陣列中的find、filter、forEach、map四個語法很相近,為了方便記憶,真正的掌握它們的用法,所以就把它們總結在一起嘍。find():返回通過測試的陣列的第一個元素的值在第一次呼叫 callback 函式時會確定元素的索引範圍,因此在 find 方法開始執行之後新
【iOS開發必收藏】詳解iOS應用程式內使用IAP/StoreKit付費、沙盒(SandBox)測試、建立測試賬號流程!【2012-12-11日更新獲取"產品付費數量等於0的問題"】
//——2012-12-11日更新 獲取"產品付費數量等於0這個問題"的原因 看到很多童鞋問到,為什麼每次都返回數量等於0?? 其實有童鞋已經找到原因了,原因是你在 ItunesConnect 裡的 “Contracts,
Android程式入口ActivityThread和Android應用程式啟動流程詳解
大家初學java時候都知道java的程式入口是從main方法進入,那麼Android是基於java編寫的,那Android的程式入口做了哪些操作呢?還有Android的應用程式到底是怎樣啟動的呢?我們一起來看一下. 首先附上ActivityThread.
Excel資料分析與業務建模_第四章_匹配函式MATCH(語法詳解及應用例項)
如果有一天,EXCEL中沒有了LOOKUP函式,怎麼辦?答案是就靠MATCH和INDEX兩兄弟了。 MATCH函式可返回指定區域內指定內容所在的行號(縱向區域)或列號(橫向區域)。 Suppose you have a worksheet with 5,000 rows c
AFNetworking3.1.0原始碼分析(四)詳解AFHTTPRequestSerializer 之初始化方法
1:類圖介紹 在AFHTTPSessionManager 初始化方法中可以看到 AFNetworking 預設使用的網路請求序列化類是AFHTTPRequestSerializer,一下是關於它的類圖: 2:類功能分析: 一:初始化函式: - (instancetyp
Netty4.x 原始碼實戰系列(二):服務端bind流程詳解
在上一篇《ServerBootstrap 與 Bootstrap 初探》中,我們已經初步的瞭解了ServerBootstrap是netty進行服務端開發的引導類。 且在上一篇的服務端示例中,我們也看到了,在使用netty進行網路程式設計時,我們是通過bind方法
UCOSII啟動流程詳解(結合原始碼分析)
μC/OS-Ⅱ初始化 在呼叫μC/OS-Ⅱ的任何其它服務之前,μC/OS-Ⅱ要求使用者首先呼叫系統初始化函式 OSIint()。OSIint()初始化μC/OS-Ⅱ所有的變數和資料結構(見 OS_CORE.C)。OSInit()建立空閒任務 idle task,這個任務總是
Spark運算元執行流程詳解之四
針對RDD的每個分割槽進行處理,返回一個新的RDD /** * Return a new RDD by applying a function to each partition of this RDD. * * `preservesPartitioning` indicates whether t
Jvm(jdk8)原始碼分析1-java命令啟動流程詳解
1.概述現在大多數網際網路公司都是使用java技術體系搭建自己的系統,所以對java開發工程師以及java系統架構師的需求非常的多,雖然普遍的要求都是需要熟悉各種java開發框架(如目前比較流行ssi或者ssh框架),但是對於java語言本身的理解才是本質。如果你熟悉jvm原
spark core原始碼分析15 Shuffle詳解-寫流程
Shuffle是一個比較複雜的過程,有必要詳細剖析一下內部寫的邏輯 ShuffleManager分為SortShuffleManager和HashShuffleManager 一、SortShu
Mybatis 整體流程詳解、部分原始碼解讀以及運用到了哪些設計模式
MyBatis主要的類 Configuration MyBatis所有的配置資訊都維持在Configuratio