libevent 之IO事件循環

看這篇之前可以看這篇基礎
libevent 學習準備
接下來就開始吧:
先來個例子

來源 https://blog.csdn.net/luotuo44/article/details/39670221

void accept_cb(int fd, short events, void* arg);
int tcp_server_init(int port, int listen_num);
int main(int argc, char** argv)
{
    int listener = tcp_server_init(9999, 10);
    if( listener == -1 )
    {
        perror(" tcp_server_init error ");
        return -1;
    }
    struct event_base* base = event_base_new();
    //添加監聽客戶端請求連接事件
    struct event* ev_listen = event_new(base, listener, EV_READ | EV_PERSIST,
                                        accept_cb, base);
    event_add(ev_listen, NULL);
    event_base_dispatch(base);
    event_base_free(base);
    return 0;
}
void accept_cb(int fd, short events, void* arg)
{
   ...
}
   
typedef struct sockaddr SA;
int tcp_server_init(int port, int listen_num)
{
    int errno_save;
    evutil_socket_t listener;
 
    listener = ::socket(AF_INET, SOCK_STREAM, 0);
    if( listener == -1 )
        return -1;
 
    //允許多次綁定同一個地址。要用在socket和bind之間
    evutil_make_listen_socket_reuseable(listener);
 
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    sin.sin_port = htons(port);
 
    if( ::bind(listener, (SA*)&sin, sizeof(sin)) < 0 )
        goto error;
 
    if( ::listen(listener, listen_num) < 0)
        goto error;
 
 
    //跨平臺統一接口,將套接字設置為非阻塞狀態
    evutil_make_socket_nonblocking(listener);
 
    return listener;
 
    error:
        errno_save = errno;
        evutil_closesocket(listener);
        errno = errno_save;
 
        return -1;
}


這里,我們反向來分析,我們先不管怎么初始化變量我們先看accept_cb,這個會回調函數怎么觸發的。
先看這個函數 event_base_dispatch 位于event.c

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;
     ...
        event_queue_make_later_events_active(base); // 這個下次講
        res = evsel->dispatch(base, tv_p);  // 重點
                timeout_process(base);  //  下次說,這個不用管

        if (N_ACTIVE_CALLBACKS(base)) {
            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;
    }
    ...
}

evsel 是結構體如下

/** Structure to define the backend of a given event_base. */
struct eventop {
    const char *name;
    void *(*init)(struct event_base *);
    int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
    int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
    int (*dispatch)(struct event_base *, struct timeval *);
    void (*dealloc)(struct event_base *);
    int need_reinit;
    enum event_method_feature features;
    size_t fdinfo_len;
};

evsel 是結構體變量,我們關心的是他的初始化,那么他的初始化在哪里呢?
evsel 是base->evsel; base 是我們這里創建的, struct event_base* base = 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;
}

struct event_base *
event_base_new_with_config(const struct event_config *cfg) {
    ...
   for (i = 0; eventops[i] && !base->evbase; i++) {
    ...
        base->evsel = eventops[i];
        base->evbase = base->evsel->init(base); [1]
    }
  ...

}

/* Array of backends in order of preference. */
static const struct eventop *eventops[] = {
#ifdef EVENT__HAVE_EVENT_PORTS
    &evportops,
#endif
#ifdef EVENT__HAVE_WORKING_KQUEUE
    &kqops,
#endif
#ifdef EVENT__HAVE_EPOLL
    &epollops,
#endif
#ifdef EVENT__HAVE_DEVPOLL
    &devpollops,
#endif
#ifdef EVENT__HAVE_POLL
    &pollops,
#endif
#ifdef EVENT__HAVE_SELECT
    &selectops,
#endif
#ifdef _WIN32
    &win32ops,
#endif
    NULL
};

eventops 是全局變量,看到這里,大家應該就懂了,具體的實現就是eventops這個數組挨個取,取到了初始化成功,后面就不用了,當然可以自定義,具體這里不說了,這里繼續分析事件循環
我們這個系統,libevent 用的是epoll,所以我們看下eopll.c 這個文件,
epoll.c 提供了如下方法,實現了struct eventop

const struct eventop epollops = {
    "epoll",
    epoll_init, 
    epoll_nochangelist_add,
    epoll_nochangelist_del,
    epoll_dispatch,
    epoll_dealloc,
    1, /* need reinit */
    EV_FEATURE_ET|EV_FEATURE_O1|EV_FEATURE_EARLY_CLOSE,
    0
};

epoll_init, 這個我們剛剛調用了,是這里 [1] 調用了
base->evbase = base->evsel->init(base); [1] 就在event_base_new_with_config,看仔細點.
我先不說這個,
我關心這個函數,
epoll_dispatch 對應res = evsel->dispatch(base, tv_p); // 重點
終于說回來了,接下來看這個

static int
epoll_dispatch(struct event_base *base, struct timeval *tv)
{
    struct epollop *epollop = base->evbase;
    struct epoll_event *events = epollop->events;
    int i, res;
    long timeout = -1;
    if (tv != NULL) {
        timeout = evutil_tv_to_msec_(tv);
        if (timeout < 0 || timeout > MAX_EPOLL_TIMEOUT_MSEC) {
            /* Linux kernels can wait forever if the timeout is
             * too big; see comment on MAX_EPOLL_TIMEOUT_MSEC. */
            timeout = MAX_EPOLL_TIMEOUT_MSEC;
        }
    }

    epoll_apply_changes(base);
    event_changelist_remove_all_(&base->changelist, base);

    EVBASE_RELEASE_LOCK(base, th_base_lock);

    res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
      ...
    for (i = 0; i < res; i++) {
        int what = events[i].events;
        short ev = 0;
        if (what & (EPOLLHUP|EPOLLERR)) {
            ev = EV_READ | EV_WRITE;
        } else {
            if (what & EPOLLIN)
                ev |= EV_READ;
            if (what & EPOLLOUT)
                ev |= EV_WRITE;
            if (what & EPOLLRDHUP)
                ev |= EV_CLOSED;
        }
                ...
        if (!ev)
            continue;

        evmap_io_active_(base, events[i].data.fd, ev | EV_ET);
    }

    if (res == epollop->nevents && epollop->nevents < MAX_NEVENT) {
        /* We used all of the event space this time.  We should
           be ready for more events next time. */
        int new_nevents = epollop->nevents * 2;
        struct epoll_event *new_events;

        new_events = mm_realloc(epollop->events,
            new_nevents * sizeof(struct epoll_event));
        if (new_events) {
            epollop->events = new_events;
            epollop->nevents = new_nevents;
        }
    }

    return (0);
}

epoll_wait 這個很熟悉了,這就是監聽這我們之前創建的listener ,listener有讀事件觸發就會就會返回了,具體是怎么讓epoll_wai 監聽的,我們知道調用epoll_ctl就可以,epoll_ctl 就是epoll_nochangelist_add 添加的,這理順便說下epoll_nochangelist_add,他 調用epoll_apply_one_change 這里面調 epoll_ctl 添加的listener ,具體大家自己可以看看,太多了說下去可能打破我的文章邏輯了,好我們接下去
evmap_io_active_(base, events[i].data.fd, ev | EV_ET);
epoll_wait 返回拿到是fd就是events[i].data.fd(fd 就是listener ),這個fd 的回調函數我們怎么拿到呢?
struct event* ev_listen = event_new(base, listener, EV_READ | EV_PERSIST, accept_cb, base);
這里我們將fd(listener ) 和 回調函數accept_cb 初始化了一個struct event,這個event,就存在base 里面,后面我說的全局base和這個base是一個就是struct event_base* base = event_base_new();,我們的東西丟這里就行了,具體在base哪里呢?接下來看

void
evmap_io_active_(struct event_base *base, evutil_socket_t fd, short events)
{
    struct event_io_map *io = &base->io;
    struct evmap_io *ctx;
    struct event *ev;

#ifndef EVMAP_USE_HT
    if (fd < 0 || fd >= io->nentries)
        return;
#endif
    GET_IO_SLOT(ctx, io, fd, evmap_io);
    /*
    (ctx) = (struct evmap_io *)((io)->entries[fd])
    */

    if (NULL == ctx)
        return;
    LIST_FOREACH(ev, &ctx->events, ev_io_next) {
        if (ev->ev_events & events)
            event_active_nolock_(ev, ev->ev_events & events, 1);
    }
}

這里GET_IO_SLOT(ctx, io, fd, evmap_io); /* (ctx) = (struct evmap_io *)((io)->entries[fd]) */
是獲取struct evmap_io *ctx;他存在struct event_io_map *io = &base->io; 里面

event_io_map 和evmap_io 具體的分析,我這里不說了,我推薦的那兩個專欄有詳細解析,
我們拿到evmap_io 這個結構體還是在貼下

struct evmap_io {
    struct event_dlist events;
    ev_uint16_t nread;
    ev_uint16_t nwrite;
    ev_uint16_t nclose;
};

events 是個tailq的鏈表,我們可以把他當個雙鏈表就行了,具體可以看我開頭說的,總之他存著我們之前創建的struct event* ev_listen = event_new(base, listener, EV_READ | EV_PERSIST, accept_cb, base);
注意我們是根據fd,io 獲取到的,這里真的值得花大時間了解下,具體怎么添加到base->io;的可以看event_add(ev_listen, NULL); 上面我說過自己看的

接著講


void
event_active_nolock_(struct event *ev, int res, short ncalls)
{
    struct event_base *base;

    ...

    base = ev->ev_base;
    ...

    switch ((ev->ev_flags & (EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
    default:
    case EVLIST_ACTIVE|EVLIST_ACTIVE_LATER:
        EVUTIL_ASSERT(0);
        break;
    case EVLIST_ACTIVE:
        /* We get different kinds of events, add them together */
        ev->ev_res |= res;
        return;
    case EVLIST_ACTIVE_LATER:
        ev->ev_res |= res;
        break;
    case 0:
        ev->ev_res = res;
        break;
    }
...
    event_callback_activate_nolock_(base, event_to_event_callback(ev));
}

event_active_nolock_,因為參數ev 帶了base 所以這個函數沒有帶base進來了,z在看
event_callback_activate_nolock_這個函數有個參數,event_to_event_callback(ev)

static inline struct event_callback *
event_to_event_callback(struct event *ev)
{
    return &ev->ev_evcallback;
}
struct event_callback {
    TAILQ_ENTRY(event_callback) evcb_active_next;
    short evcb_flags;
    ev_uint8_t evcb_pri;    /* smaller numbers are higher priority */
    ev_uint8_t evcb_closure;
    /* allows us to adopt for different types of events */
        union {
        void (*evcb_callback)(evutil_socket_t, short, void *);
        void (*evcb_selfcb)(struct event_callback *, void *);
        void (*evcb_evfinalize)(struct event *, void *);
        void (*evcb_cbfinalize)(struct event_callback *, void *);
    } evcb_cb_union;
    void *evcb_arg;
};

struct event {
    struct event_callback ev_evcallback;

    /* for managing timeouts */
    union {
        TAILQ_ENTRY(event) ev_next_with_common_timeout;
        int min_heap_idx;
    } ev_timeout_pos;
    evutil_socket_t ev_fd;

    struct event_base *ev_base;

    union {
        /* used for io events */
        struct {
            LIST_ENTRY (event) ev_io_next;
            struct timeval ev_timeout;
        } ev_io;

        /* used by signal events */
        struct {
            LIST_ENTRY (event) ev_signal_next;
            short ev_ncalls;
            /* Allows deletes in callback */
            short *ev_pncalls;
        } ev_signal;
    } ev_;

    short ev_events;
    short ev_res;       /* result passed to event callback */
    struct timeval ev_timeout;
};

可以看到這里把event強轉成了event_callback ,好了我們接下來看


int
event_callback_activate_nolock_(struct event_base *base,
    struct event_callback *evcb)
{
    int r = 1;

    if (evcb->evcb_flags & EVLIST_FINALIZING)
        return 0;

    switch (evcb->evcb_flags & (EVLIST_ACTIVE|EVLIST_ACTIVE_LATER)) {
    default:
        EVUTIL_ASSERT(0);
        EVUTIL_FALLTHROUGH;
    case EVLIST_ACTIVE_LATER:
        event_queue_remove_active_later(base, evcb);
        r = 0;
        break;
    case EVLIST_ACTIVE:
        return 0;
    case 0:
        break;
    }

    event_queue_insert_active(base, evcb);

    if (EVBASE_NEED_NOTIFY(base))
        evthread_notify_base(base);

    return r;
}

static void
event_queue_insert_active(struct event_base *base, struct event_callback *evcb)
{
    EVENT_BASE_ASSERT_LOCKED(base);

    if (evcb->evcb_flags & EVLIST_ACTIVE) {
        /* Double insertion is possible for active events */
        return;
    }

    INCR_EVENT_COUNT(base, evcb->evcb_flags);

    evcb->evcb_flags |= EVLIST_ACTIVE;

    base->event_count_active++;
    MAX_EVENT_COUNT(base->event_count_active_max, base->event_count_active);
    EVUTIL_ASSERT(evcb->evcb_pri < base->nactivequeues);
    TAILQ_INSERT_TAIL(&base->activequeues[evcb->evcb_pri],
        evcb, evcb_active_next);
}

event_callback_activate_nolock_ 這個函數就是把event_callback 插入到base的activequeues鏈表,然后就結束了。
總結下,epoll_wait 返回后,根據fd 在全局base中找到event,然后轉成event_callback 插入到全局base 的activequeues,那什么時候從activequeues 取出來執行函數呢?
接下來看回event_base_dispatch

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;
     ...
        event_queue_make_later_events_active(base); // 這個下次講
        res = evsel->dispatch(base, tv_p);  // 重點
                timeout_process(base);  //  下次說,這個不用管

        if (N_ACTIVE_CALLBACKS(base)) {
            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;
    }
    ...
}

我們說完了 res = evsel->dispatch(base, tv_p); // 重點,這個把創建的event(激活的) 轉成event_callback 插入到全局base 的activequeues,
下面event_process_active(base); // 重點就簡單了


static int
event_process_active(struct event_base *base)
{
    /* Caller must hold th_base_lock */
    struct evcallback_list *activeq = NULL;
    int i, c = 0;
    
    ...

    for (i = 0; i < base->nactivequeues; ++i) {
        if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {
            base->event_running_priority = i;
            activeq = &base->activequeues[i];
            if (i < limit_after_prio)
                c = event_process_active_single_queue(base, activeq,
                    INT_MAX, NULL);
            else
                c = event_process_active_single_queue(base, activeq,
                    maxcb, endtime);
            if (c < 0) {
                goto done;
            } else if (c > 0)
                break; /* Processed a real event; do not
                    * consider lower-priority events */
            /* If we get here, all of the events we processed
             * were internal.  Continue. */
        }
    }

done:
    base->event_running_priority = -1;

    return c;
}


static int
event_process_active_single_queue(struct event_base *base,
    struct evcallback_list *activeq,
    int max_to_process, const struct timeval *endtime)
{
    struct event_callback *evcb;
    int count = 0;

...

    for (evcb = TAILQ_FIRST(activeq); evcb; evcb = TAILQ_FIRST(activeq)) {
        struct event *ev=NULL;
        if (evcb->evcb_flags & EVLIST_INIT) {
            ev = event_callback_to_event(evcb); // 這里轉回來了

            if (ev->ev_events & EV_PERSIST || ev->ev_flags & EVLIST_FINALIZING)
                event_queue_remove_active(base, evcb);
            else
                event_del_nolock_(ev, EVENT_DEL_NOBLOCK);
            ...
        } else {
            event_queue_remove_active(base, evcb);
            event_debug(("event_process_active: event_callback %p, "
                "closure %d, call %p",
                evcb, evcb->evcb_closure, evcb->evcb_cb_union.evcb_callback));
        }

        if (!(evcb->evcb_flags & EVLIST_INTERNAL))
            ++count;


        base->current_event = evcb;

...
        switch (evcb->evcb_closure) {
        case EV_CLOSURE_EVENT_SIGNAL:
            EVUTIL_ASSERT(ev != NULL);
            event_signal_closure(base, ev);
            break;
        case EV_CLOSURE_EVENT_PERSIST:
            EVUTIL_ASSERT(ev != NULL);
            event_persist_closure(base, ev);
            break;
        case EV_CLOSURE_EVENT: {
            void (*evcb_callback)(evutil_socket_t, short, void *);
            short res;
            EVUTIL_ASSERT(ev != NULL);
            evcb_callback = *ev->ev_callback;
            res = ev->ev_res;
            EVBASE_RELEASE_LOCK(base, th_base_lock);
            evcb_callback(ev->ev_fd, res, ev->ev_arg);
        }
        break;
    
    ...
        break;
        default:
            EVUTIL_ASSERT(0);
        }

    

        ...
    }
    return count;
}

這里就是取出event,執行event的回調函數,然后我們的accept_cb 就被調用了。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372