<span >想學習研究libevent怎麼設計的,學習它的思想,學習它的設計,奈何自己實力不夠啊,於是另辟奇徑,從最早的版本開始,一個版本一個版本的學習,不信吃不透它。</span>
struct event { TAILQ_ENTRY (event) ev_read_next; TAILQ_ENTRY (event) ev_write_next; TAILQ_ENTRY (event) ev_timeout_next; TAILQ_ENTRY (event) ev_add_next; int ev_fd; short ev_events; struct timeval ev_timeout; void (*ev_callback)(int, short, void *arg); void *ev_arg; int ev_flags; };以上為事件的結構體,libevent通過這個結構體管理事件
void event_init(void);
int event_dispatch(void); int timeout_next(struct timeval *); void timeout_process(void); void event_set(struct event *, int, short, void (*)(int, short, void *), void *); void event_add(struct event *, struct timeval *); void event_del(struct event *); int event_pending(struct event *, short, struct timeval *);以上為libevent提供的接口,下面我們一個一個的詳細分析他們
首先,事件初始化,初始化libevent
/*四個隊列的頭指針*/ TAILQ_HEAD (timeout_list, event) timequeue; TAILQ_HEAD (event_wlist, event) writequeue; TAILQ_HEAD (event_rlist, event) readqueue; TAILQ_HEAD (event_ilist, event) addqueue;
接著是libevent的核心循環函數
/* 事件處理的主循環:int event_dispatch(void) 功能描述:事件處理的主循環,循環監聽,調用事件的回調函數處理 函數實現:1.開始循環之前,先調用函數event_recalc()分配合適的fd_set 2.進入處理循環 (1).遍歷讀事件隊列和寫事件隊列,將描述符添加到fd_set中 (2).調用timeout_next(),得到阻塞等待時間 (3).調用select監聽 (4).遍歷讀寫隊列,看看有那個事件被觸發,調用對應的回調函數,並將其從隊列中移出 如果沒有被觸發,它們將仍被保留在隊列中,這個時候我們從它們中再次找到最大文件描述符 並且,在處理期間,要將inloop標記置位,防止在讀寫隊列期間,再將新的事件移入隊列 (5).處理完之後,遍歷add隊列,將add隊列中的事件,對應投入讀或者寫隊列中 (6).根據新的最大描述符,調用event_recalc()重新分配fd_set空間 (7).調用timeout_process()處理 */ int event_dispatch(void) { struct timeval tv; struct event *ev, *old; int res, maxfd; /* Calculate the initial events that we are waiting for */ //1.開始循環之前,重新計算分配描述符集合大小 if (events_recalc(0) == -1) return (-1); while (1) { //將讀寫事件集全部清0,類似於FD_ZERO memset(event_readset, 0, event_fdsz); memset(event_writeset, 0, event_fdsz); //遍歷寫事件集合隊列 TAILQ_FOREACH(ev, &writequeue, ev_write_next) //將寫事件集合中的描述符,添加道select監聽的寫數據集 FD_SET(ev->ev_fd, event_writeset); //遍歷讀事件集合隊列 TAILQ_FOREACH(ev, &readqueue, ev_read_next) //遍歷讀事件集合隊列,將事件描述符添加到select監聽 FD_SET(ev->ev_fd, event_readset); //時間設置 timeout_next(&tv); if ((res = select(event_fds + 1, event_readset, event_writeset, NULL, &tv)) == -1) { if (errno != EINTR) { log_error("select"); return (-1); } continue; } LOG_DBG((LOG_MISC, 80, __FUNCTION__": select reports %d", res)); maxfd = 0; event_inloop = 1; for (ev = TAILQ_FIRST(&readqueue); ev;) { //從讀事件隊頭拿出讀事件 old = TAILQ_NEXT(ev, ev_read_next); if (FD_ISSET(ev->ev_fd, event_readset)) { //看看這個事件是否就緒 event_del(ev); //從事件隊列中刪除事件 (*ev->ev_callback)(ev->ev_fd, EV_READ, ev->ev_arg); //調用事件注冊的處理函數 } else if (ev->ev_fd > maxfd) //否則找最大描述符 maxfd = ev->ev_fd; ev = old; } for (ev = TAILQ_FIRST(&writequeue); ev;) { old = TAILQ_NEXT(ev, ev_read_next); if (FD_ISSET(ev->ev_fd, event_writeset)) { event_del(ev); (*ev->ev_callback)(ev->ev_fd, EV_WRITE, ev->ev_arg); } else if (ev->ev_fd > maxfd) maxfd = ev->ev_fd; ev = old; } event_inloop = 0; for (ev = TAILQ_FIRST(&addqueue); ev; ev = TAILQ_FIRST(&addqueue)) { TAILQ_REMOVE(&addqueue, ev, ev_add_next); ev->ev_flags &= ~EVLIST_ADD; event_add_post(ev); if (ev->ev_fd > maxfd) maxfd = ev->ev_fd; } if (events_recalc(maxfd) == -1) return (-1); timeout_process(); } return (0); }在這個主循環函數中,它調用了
events_recalc()這個函數,那麼這個函數是干嘛的呢?
<pre name="code" class="cpp">/* 分配事件集fd_set大小的函數:int event_recalc(int max) 功能描述:根據,最大事件描述符,分配對應大小的事件集,在unix中,fd_set是通過對應位表示事件描述符的,所以,事件集的大小要足夠大 參數為最大描述符,根據所傳入的參數,分配最大fd_set空間 如果,傳入的參數為0,函數會自己遍歷讀寫隊列,找到最大描述符,分配fd_set空間 函數實現:1.將傳入的參數,賦值給存儲最大描述符的全局變量event_fds 2.判斷最大描述符是否為0,是就遍歷讀寫隊列,找到隊列中最大的描述符 3.計算,表示最大描述符,所需要的fd_set字節數 4.全局變量event_fdsz存儲當前fd_set的字節大小,將最新計算出的fd_set大小和event_fdsz進行比較 如果,最新計算出的所需大小大於當前fd_set的大小,就重新分配讀寫集合的空間 更新全局變量event_fds,event_fdsz,event_readset,event_writeset的值 */ int events_recalc(int max) { //讀寫,描述符·集合 fd_set *readset, *writeset; //描述事件的結構體 struct event *ev; int fdsz; //最大文件描述符在描述符集合中 event_fds = max; //如果最大傳入描述符為0 if (!event_fds) { //在寫隊列中遍歷找最大描述符 TAILQ_FOREACH(ev, &writequeue, ev_write_next) if (ev->ev_fd > event_fds) event_fds = ev->ev_fd; //再去遍歷讀隊列 TAILQ_FOREACH(ev, &readqueue, ev_read_next) if (ev->ev_fd > event_fds) event_fds = ev->ev_fd; //最後event_fds中是最大描述符 } //得到fd_set占字節數 fdsz = howmany(event_fds + 1, NFDBITS) * sizeof(fd_mask); if (fdsz > event_fdsz) { if ((readset = realloc(event_readset, fdsz)) == NULL) { log_error("malloc"); return (-1); } if ((writeset = realloc(event_writeset, fdsz)) == NULL) { log_error("malloc"); free(readset); return (-1); } memset(readset + event_fdsz, 0, fdsz - event_fdsz); memset(writeset + event_fdsz, 0, fdsz - event_fdsz); event_readset = readset; event_writeset = writeset; event_fdsz = fdsz; } return (0); }/*
初始化事件:void event_set(struct event *ev, int fd, short events,void (*callback)(int, short, void *), void *arg)
功能描述:設置所傳入的事件結構體的屬性,完成對事件的初始化
函數實現:1.設置事件的回調函數
2.設置事件的參數ev_arg,它是回調函數的第三個參數
3.設置事件的參數ev_fd,它是回調函數的第一個參數
4.設置事件的參數ev_events,它是回調的第二個參數,也代表事件的類型(讀、寫、超時)
*/
void
event_set(struct event *ev, int fd, short events,
void (*callback)(int, short, void *), void *arg)
{
ev->ev_callback = callback;
ev->ev_arg = arg;
ev->ev_fd = fd;
ev->ev_events = events;
ev->ev_flags = EVLIST_INIT;
}
/* <span > </span>將事件添加到對應的讀或者寫隊列:void event_add(struct event*ev,struct timeval* tv) <span > </span>功能描述:將事件添加到對應的讀寫隊列中 <span > </span>函數實現:1.如果阻塞等待時間不為空 <span > </span>(1).得到當前時間 <span > </span>(2).將當前時間與阻塞時間相加得到超時時間 <span > </span>(3).判斷事件是否在超時隊列中 <span > </span>如果在超時隊列中,將事件從隊列中移出 <span > </span>(4).在超時隊列中尋找合適的位置(超時隊列,將超時時間從小到大排列) <span > </span>如果找到合適的位置,將其插入到該位置 <span > </span>如果沒有找到,將其從隊尾插入 <span > </span>(5).將事件在超時隊列的標記置位 <span > </span> 2.如果事件正在處理中 <span > </span>(1).通過檢測標記位判斷當前被插入的事件是否之前就已經在add隊列中 <span > </span>如果已經在隊列中,直接返回 <span > </span>如果沒有,將其加入add隊列中,並將其標記置位 <span > </span> 3.否則 <span > </span>(1).調用event_add_post()將其加入到對應的讀寫隊列中 */ void event_add(struct event *ev, struct timeval *tv) { <span > </span>LOG_DBG((LOG_MISC, 55, <span > </span> "event_add: event: %p, %s%s%scall %p", <span > </span> ev, <span > </span> ev->ev_events & EV_READ ? "EV_READ " : " ", <span > </span> ev->ev_events & EV_WRITE ? "EV_WRITE " : " ", <span > </span> tv ? "EV_TIMEOUT " : " ", <span > </span> ev->ev_callback)); <span > </span> <span > </span>//如果等待時間不為空 <span > </span>if (tv != NULL) { <span > </span>struct timeval now; <span > </span>struct event *tmp; <span > </span> <span > </span>//1.得到當前時間 <span > </span>gettimeofday(&now, NULL); <span > </span>//2.將當前時間與阻塞等待時間相加,得到超時時間 <span > </span>timeradd(&now, tv, &ev->ev_timeout); <span > </span>LOG_DBG((LOG_MISC, 55, <span > </span> "event_add: timeout in %d seconds, call %p", <span > </span> tv->tv_sec, ev->ev_callback)); <span > </span>//如果有超時隊列標記(即之前在隊列中存在) <span > </span>if (ev->ev_flags & EVLIST_TIMEOUT) <span > </span>//將事件從時間隊列移出 <span > </span>TAILQ_REMOVE(&timequeue, ev, ev_timeout_next); <span > </span>/* Insert in right temporal order */ <span > </span>//再次將時間事件找到正確的隊列位置插入 <span > </span>for (tmp = TAILQ_FIRST(&timequeue); tmp; <span > </span> tmp = TAILQ_NEXT(tmp, ev_timeout_next)) { <span > </span> if (timercmp(&ev->ev_timeout, &tmp->ev_timeout, <=)) <span > </span> break; <span > </span>} <span > </span>if (tmp) <span > </span>TAILQ_INSERT_BEFORE(tmp, ev, ev_timeout_next); <span > </span>else <span > </span>TAILQ_INSERT_TAIL(&timequeue, ev, ev_timeout_next); <span > </span>//再將時間隊列標記置位 <span > </span>ev->ev_flags |= EVLIST_TIMEOUT; <span > </span>} <span > </span> <span > </span>//如果事件正在循環中,判斷一下被插入的事件是否是從添加等待隊列中取出來的,如果是,直接返回,不是,將它添加到等待隊列返回 <span > </span>if (event_inloop) { <span > </span>/* We are in the event loop right now, we have to <span > </span> * postpone the change until later. <span > </span> */ <span > </span>if (ev->ev_flags & EVLIST_ADD) <span > </span>return; <span > </span>TAILQ_INSERT_TAIL(&addqueue, ev, ev_add_next); <span > </span>ev->ev_flags |= EVLIST_ADD; <span > </span>} else <span > </span>event_add_post(ev); }
/* <span > </span>將事件添加到對應的讀或者寫隊列 <span > </span>功能描述:通過判斷事件的ev_events標記將其放入對應的隊列 <span > </span>函數實現:1.判斷事件是讀事件,並且事件沒有在read隊列中,將其添加到讀隊列,讀隊列標記置位 <span > </span> 2.判斷事件是寫事件,並且事件沒有在write隊列中,將其添加到寫隊列,寫隊列標記置位 */ void event_add_post(struct event *ev) { <span > </span>//如果,事件是讀事件,並且沒有在讀隊列中,就添加到讀隊列 <span > </span>if ((ev->ev_events & EV_READ) && !(ev->ev_flags & EVLIST_READ)) { <span > </span>TAILQ_INSERT_TAIL(&readqueue, ev, ev_read_next); <span > </span> <span > </span>ev->ev_flags |= EVLIST_READ; <span > </span>} <span > </span>//如果,事件為寫事件,並且沒有在寫隊列,就添加到寫隊列 <span > </span>if ((ev->ev_events & EV_WRITE) && !(ev->ev_flags & EVLIST_WRITE)) { <span > </span>TAILQ_INSERT_TAIL(&writequeue, ev, ev_write_next); <span > </span> <span > </span>ev->ev_flags |= EVLIST_WRITE; <span > </span>} } /* <span > </span>刪除事件 <span > </span>功能描述:把事件從所在的隊列中清除 <span > </span>函數實現:1.判斷事件在讀或者寫或者添加或者超時隊列中,將其從對應隊列中刪除,將其flag位置0 */ void event_del(struct event *ev) { <span > </span>LOG_DBG((LOG_MISC, 80, "event_del: %p, callback %p", <span > </span> ev, ev->ev_callback)); <span > </span>if (ev->ev_flags & EVLIST_ADD) { <span > </span>TAILQ_REMOVE(&addqueue, ev, ev_add_next); <span > </span>ev->ev_flags &= ~EVLIST_ADD; <span > </span>} <span > </span>if (ev->ev_flags & EVLIST_TIMEOUT) { <span > </span>TAILQ_REMOVE(&timequeue, ev, ev_timeout_next); <span > </span>ev->ev_flags &= ~EVLIST_TIMEOUT; <span > </span>} <span > </span>if (ev->ev_flags & EVLIST_READ) { <span > </span>TAILQ_REMOVE(&readqueue, ev, ev_read_next); <span > </span>ev->ev_flags &= ~EVLIST_READ; <span > </span>} <span > </span>if (ev->ev_flags & EVLIST_WRITE) { <span > </span>TAILQ_REMOVE(&writequeue, ev, ev_write_next); <span > </span>ev->ev_flags &= ~EVLIST_WRITE; <span > </span>} } /* <span > </span>獲得新的阻塞時間 <span > </span>功能描述:獲得新的阻塞時間 <span > </span>函數實現:1.如果超時隊列沒有元素,返回默認超時時間 <span > </span> 2.如果有事件 <span > </span>(1).得到當前時間 <span > </span>(2).和隊頭事件的超時時間比較,如果已經超時,說明出現問題,直接清空超時時間,不阻塞 <span > </span>(3).沒超時,就將剩余的阻塞時間計算出來,作為新的阻塞時間 */ int timeout_next(struct timeval *tv) { <span > </span>//當前時間 <span > </span>struct timeval now; <span > </span>//指向事件的指針 <span > </span>struct event *ev; <span > </span> <span > </span>//定時器隊頭事件指針是否為空 <span > </span>if ((ev = TAILQ_FIRST(&timequeue)) == NULL) { <span > </span>//清空時間 <span > </span>timerclear(tv); <span > </span>//定時默認5,返回 <span > </span>tv->tv_sec = TIMEOUT_DEFAULT; <span > </span>return (0); <span > </span>} <span > </span> <span > </span> <span > </span>//得到當前時間 <span > </span>if (gettimeofday(&now, NULL) == -1) <span > </span>return (-1); <span > </span> <span > </span>//和定時器隊頭事件定義的超時時間比較 <span > </span>if (timercmp(&ev->ev_timeout, &now, <=)) { <span > </span>//不超時,清空結構 <span > </span>timerclear(tv); <span > </span>return (0); <span > </span>} <span > </span>//定時器事件超時出的時間,更新到tv中 <span > </span>timersub(&ev->ev_timeout, &now, tv); <span > </span>LOG_DBG((LOG_MISC, 60, "timeout_next: in %d seconds", tv->tv_sec)); <span > </span>return (0); } /* <span > </span>超時後的處理 <span > </span>功能描述:將超時隊列中所有超時的事件從隊列中移出,並且執行其對應的回調 <span > </span>函數實現:1.獲得當前時間 <span > </span> 2.只要超時隊列不為空,進入到隊列中 <span > </span>(1).從隊頭獲得的超時時間比當前時間大,說明整個隊列中都沒有超時,跳出循環 <span > </span>(2).否則,將隊頭從隊列中移出,將隊頭事件的超時隊列標記位置0,調用對應的超時回調處理,繼續循環 */ void timeout_process(void) { <span > </span>struct timeval now; <span > </span>struct event *ev; <span > </span>gettimeofday(&now, NULL); <span > </span>while ((ev = TAILQ_FIRST(&timequeue)) != NULL) { <span > </span>if (timercmp(&ev->ev_timeout, &now, >)) <span > </span>break; <span > </span>TAILQ_REMOVE(&timequeue, ev, ev_timeout_next); <span > </span>ev->ev_flags &= ~EVLIST_TIMEOUT; <span > </span>LOG_DBG((LOG_MISC, 60, "timeout_process: call %p", <span > </span> ev->ev_callback)); <span > </span>(*ev->ev_callback)(ev->ev_fd, EV_TIMEOUT, ev->ev_arg); <span > </span>} }