1. 程式人生 > 其它 >libenvent設計思路與實現細節--轉

libenvent設計思路與實現細節--轉

libevent 流程圖

1 libevent的設計思路

設計思路做到統一
統一事件型別:訊號事件,定時事件,IO時間
統一系統呼叫:epoll/ select/ poll/ win 等作為底層 去實現不同的eventop(事件多路分發器)的介面

struct eventop 
{
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*dispatch)(struct event_base *, struct timeval *);
}
 

組織形式是reactor模式


反轉即cb。每個檔案描述符上的可讀/可寫事件可以建立多個事件處理器(不同的回撥函式),libevent的IO事件佇列將相同檔案描述符的事件處理器組織在一起(event_io_map),當事件就緒時,可以根據fd找到對應的IO事件佇列(evmap_io),將佇列中的每一個節點(event)按照優先順序插入到不同的啟用佇列中

struct epollop {
	struct epoll_event *events; // reactor(event_base)監聽的所有的事件(事件數>檔案描述符數)
	int nevents;
	int epfd;
};

struct evmap_io
{
	struct
event_list events; // 檔案描述符上所有的事件 ev_uint16_t nread; ev_uint16_t nwrite; } struct event_base { const struct eventop *evsel; // 多路複用的底層系統實現 struct event_io_map io; // evmap_io[fd] idx:fd --> evmap_io fd到events的對映 struct event_list eventqueue; // 註冊事件佇列 struct event_list *activequeues; // 啟用佇列 struct
event_changelist changelist; //事件變化佇列。 用途:如果一個檔案描述符上註冊的事件被多次修改,則可以使用緩衝來避免重複的系統呼叫(比如epoll_ctl). }
 

2 註冊佇列和啟用佇列的作用

epoll相比於poll在於返回的就緒佇列,不需要再次遍歷,返回的就是可處理的。
同理,啟用佇列也是相同作用,啟用佇列中的都是直接可以執行cb的。

event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
	switch (queue) {
	case EVLIST_INSERTED: // event_add()呼叫
		TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
		break;
	case EVLIST_ACTIVE:   // dispatch()呼叫
		base->event_count_active++;
		TAILQ_INSERT_TAIL(&base->activequeues[ev->ev_pri],ev,ev_active_next);
		break;
	case EVLIST_TIMEOUT: 
{
		if (is_common_timeout(&ev->ev_timeout, base)) {
			struct common_timeout_list *ctl =
			    get_common_timeout_list(base, &ev->ev_timeout);
			insert_common_timeout_inorder(ctl, ev);
		} else
			min_heap_push(&base->timeheap, ev);
		break;
	}
	}
}
 

3 event_add 為什麼沒有直接呼叫epoll_ctl

你先變,穩定了我在加
event_changelist_add() —> event_changelist_get_or_construct() 只修改event_changelist,因為檔案描述符可能被多次設定監聽讀寫時間,所以在這裡只記錄變化的,在dispatch時再epoll_apply_changes()。

//自上次呼叫 eventtop.dispatch 以來的“更改”列表。
struct event_changelist {
	struct event_change *changes; 
	int n_changes; // 改變的個數
	int changes_size;
};
 

4 libevent非內部函式是什麼 | 是如何實現非阻塞的

在event_loop中

while(!done)
{
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;
}
 
/*
低優先順序的可能會執行不到,被高優先順序的啟用事件佇列餓死

啟用事件佇列同樣有優先順序,從高到低執行 對應啟用事件佇列中的每一個事件的回撥cb

實際並沒有把所有啟用事件佇列執行完畢,當佇列中有一個非內部事件(使用者自定義的)執行完畢就返回了,外層只要沒有done(主迴圈大多數時候都是要在的),還會再下一次繼續執行 
大多數時候返回的是我們處理的非內部事件的數量 為的是這個EVLOOP_ONCE引數,只執行一次event_loop
 */
static int
event_process_active(struct event_base *base)
{
	/* Caller must hold th_base_lock */
	struct event_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];
			c = event_process_active_single_queue(base, activeq);
			if (c < 0) {
				base->event_running_priority = -1;
				return -1;
			} 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. */
		}
	}

	event_process_deferred_callbacks(&base->defer_queue,&base->event_break); //執行延遲迴調函式,當所有的啟用事件都處理完成後,需要做的事情
	base->event_running_priority = -1;
	return c;
}

libevent 中的尾佇列TAILQ結構

c語言實現的雙向連結串列 queue.h 用來儲存事件 list的原型
尾佇列頭tqh 和 尾佇列元素tqe

  • 通過TAILQ_HEAD(name, type)巨集可以快速的定義某一個型別結構體的佇列.
    佇列處理方面不需要知道佇列中資料是什麼。類似queue<your_type> 這種傳入泛型。
  • 二級指標的用法
    有一個型別叫做指標的指標tqh_last,於是可以修改指標的指標儲存的指標的地址,而不是修改指標儲存的資料的地址。這樣不用儲存最後一個節點,而是最後一個節點的tqe_next指標
  • 插入/刪除都是o(1)
/* 
 * Tail queue definitions. 尾佇列定義 
 */  
#define TAILQ_HEAD(name, type)                      \  
struct name {                               \  
    struct type *tqh_first; /* first element */         \  
    struct type **tqh_last; /* addr of last next element */     \  二級指標
}  
  
#define TAILQ_HEAD_INITIALIZER(head)                    \  
    { NULL, &(head).tqh_first }  
  
#define TAILQ_ENTRY(type)                       \  
struct {                                \  
    struct type *tqe_next;  /* next element */          \  
    struct type **tqe_prev; /* address of previous next element */  \  
}  
  
/* 
 * tail queue access methods 
 */  
#define TAILQ_FIRST(head)       ((head)->tqh_first)  
#define TAILQ_END(head)         NULL  
#define TAILQ_NEXT(elm, field)      ((elm)->field.tqe_next)  
#define TAILQ_LAST(head, headname)                  \  
    (*(((struct headname *)((head)->tqh_last))->tqh_last))  
/* XXX */  
#define TAILQ_PREV(elm, headname, field)                \  
    (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))  
#define TAILQ_EMPTY(head)                       \  
    (TAILQ_FIRST(head) == TAILQ_END(head))  
  
#define TAILQ_FOREACH(var, head, field)                 \  
    for((var) = TAILQ_FIRST(head);                  \  
        (var) != TAILQ_END(head);                   \  
        (var) = TAILQ_NEXT(var, field))  
  
#define TAILQ_FOREACH_REVERSE(var, head, headname, field)       \  
    for((var) = TAILQ_LAST(head, headname);             \  
        (var) != TAILQ_END(head);                   \  
        (var) = TAILQ_PREV(var, headname, field))  
  
/* 
 * Tail queue functions. 
 */  
#define TAILQ_INIT(head) do {                       \  
    (head)->tqh_first = NULL;                    \  
    (head)->tqh_last = &(head)->tqh_first;                \  
} while (0)  
  
#define TAILQ_INSERT_HEAD(head, elm, field) do {            \  
    if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)  \  
        (head)->tqh_first->field.tqe_prev =           \  
            &(elm)->field.tqe_next;              \  
    else                                \  
        (head)->tqh_last = &(elm)->field.tqe_next;        \  
    (head)->tqh_first = (elm);                   \  
    (elm)->field.tqe_prev = &(head)->tqh_first;           \  
} while (0)  
  
#define TAILQ_INSERT_TAIL(head, elm, field) do {            \  
    (elm)->field.tqe_next = NULL;                    \  
    (elm)->field.tqe_prev = (head)->tqh_last;         \  
    *(head)->tqh_last = (elm);       \elm是一個指標, tqh_last是一個指標的指標 這裡相當於操作tqe_next了
    (head)->tqh_last = &(elm)->field.tqe_next;            \  記錄的是最後元素的下一個元素的地址,便於尾插
} while (0)  
  
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {      \  
    if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\  
        (elm)->field.tqe_next->field.tqe_prev =           \  
            &(elm)->field.tqe_next;              \  
    else                                \  
        (head)->tqh_last = &(elm)->field.tqe_next;        \  
    (listelm)->field.tqe_next = (elm);               \  
    (elm)->field.tqe_prev = &(listelm)->field.tqe_next;       \  
} while (0)  
  
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do {           \  
    (elm)->field.tqe_prev = (listelm)->field.tqe_prev;        \  
    (elm)->field.tqe_next = (listelm);               \  
    *(listelm)->field.tqe_prev = (elm);              \  
    (listelm)->field.tqe_prev = &(elm)->field.tqe_next;       \  
} while (0)  
  
#define TAILQ_REMOVE(head, elm, field) do {             \  
    if (((elm)->field.tqe_next) != NULL)             \  
        (elm)->field.tqe_next->field.tqe_prev =           \  
            (elm)->field.tqe_prev;               \  
    else                                \  
        (head)->tqh_last = (elm)->field.tqe_prev;     \  
    *(elm)->field.tqe_prev = (elm)->field.tqe_next;           \  
} while (0)  
  
#define TAILQ_REPLACE(head, elm, elm2, field) do {          \  
    if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \  
        (elm2)->field.tqe_next->field.tqe_prev =      \  
            &(elm2)->field.tqe_next;             \  
    else                                \  
        (head)->tqh_last = &(elm2)->field.tqe_next;       \  
    (elm2)->field.tqe_prev = (elm)->field.tqe_prev;           \  
    *(elm2)->field.tqe_prev = (elm2);                \  
} while (0)