1. 程式人生 > >libevent原始碼詳解(四)應用流程詳解

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