歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> libevent源碼學習研究(libevent

libevent源碼學習研究(libevent

日期:2017/3/3 12:24:26   编辑:Linux技術

<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>}
}

Copyright © Linux教程網 All Rights Reserved