redis的事件驅動模塊負責處理文件和定時器兩種任務。
下面是幾個函數指針
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
下面是文件相關的結構體
struct aeFileEvent {
int fd;
int mask; /* one of AE_(READABLE|WRITABLE|EXCEPTION) */
aeFileProc *fileProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeFileEvent *next;
} aeFileEvent;
下面是定時器結構體
struct aeTimeEvent {
long long id; /* time event identifier. */
long when_sec; /* seconds */
long when_ms; /* milliseconds */
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeTimeEvent *next;
} aeTimeEvent;
下面是事件循環的核心結構體
struct aeEventLoop {
// 定時器id,每創建一個定時器結構體,就加一
long long timeEventNextId;
// 兩個鏈表
aeFileEvent *fileEventHead;
aeTimeEvent *timeEventHead;
int stop;
} aeEventLoop;
下面先看一下一些基礎函數,然後再分析具體流程。
1 創建一個事件循環結構體
aeEventLoop *aeCreateEventLoop(void) {
aeEventLoop *eventLoop;
eventLoop = zmalloc(sizeof(*eventLoop));
if (!eventLoop) return NULL;
eventLoop->fileEventHead = NULL;
eventLoop->timeEventHead = NULL;
eventLoop->timeEventNextId = 0;
eventLoop->stop = 0;
return eventLoop;
}
2 文件相關函數
2.1 新建一個文件相關的結構體
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
aeFileEvent *fe;
fe = zmalloc(sizeof(*fe));
if (fe == NULL) return AE_ERR;
fe->fd = fd;
fe->mask = mask;
fe->fileProc = proc;
fe->finalizerProc = finalizerProc;
fe->clientData = clientData;
fe->next = eventLoop->fileEventHead;
eventLoop->fileEventHead = fe;
return AE_OK;
}
2.2 刪除一個文件相關的結構體
// 刪除某個節點(fd和mask等於入參的節點)
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
{
aeFileEvent *fe, *prev = NULL;
fe = eventLoop->fileEventHead;
while(fe) {
if (fe->fd == fd && fe->mask == mask) {
// 說明待刪除的節點是第一個節點,直接修改頭節點的指針
if (prev == NULL)
eventLoop->fileEventHead = fe->next;
else
// 修改prev節點的next指針指向當前刪除節點的下一個節點
prev->next = fe->next;
// 鉤子函數
if (fe->finalizerProc)
fe->finalizerProc(eventLoop, fe->clientData);
// 釋放待刪除節點的內存
zfree(fe);
return;
}
// 記錄上一個節點,當找到待刪除節點時,修改prev指針的next指針(如果prev非空)爲待刪除節點的下一個節點
prev = fe;
fe = fe->next;
}
}
3 定時器相關函數
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
long long id = eventLoop->timeEventNextId++;
aeTimeEvent *te;
te = zmalloc(sizeof(*te));
if (te == NULL) return AE_ERR;
te->id = id;
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
// 頭插法插入eventLoop的timeEventHead隊列
te->next = eventLoop->timeEventHead;
eventLoop->timeEventHead = te;
return id;
}
3.1 時間相關的函數
// 獲取當前時間,秒和毫秒
static void aeGetTime(long *seconds, long *milliseconds)
{
struct timeval tv;
gettimeofday(&tv, NULL);
*seconds = tv.tv_sec;
*milliseconds = tv.tv_usec/1000;
}
static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) {
long cur_sec, cur_ms, when_sec, when_ms;
// 獲取
aeGetTime(&cur_sec, &cur_ms);
// 絕對時間,秒數
when_sec = cur_sec + milliseconds/1000;
// 絕對時間,毫秒數
when_ms = cur_ms + milliseconds%1000;
// 大於一秒則進位到秒中
if (when_ms >= 1000) {
when_sec ++;
when_ms -= 1000;
}
// 返回絕對時間的秒和毫秒
*sec = when_sec;
*ms = when_ms;
}
3.2 刪除一個定時器結構體(參考刪除文件相關數據結構的函數)
// 刪除一個timeEvent節點
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{
aeTimeEvent *te, *prev = NULL;
te = eventLoop->timeEventHead;
while(te) {
if (te->id == id) {
if (prev == NULL)
eventLoop->timeEventHead = te->next;
else
prev->next = te->next;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
return AE_OK;
}
prev = te;
te = te->next;
}
return AE_ERR; /* NO event with the specified ID found */
}
3.3 查找最快到期的定時器節點
// 找出最快到期的節點
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
{
aeTimeEvent *te = eventLoop->timeEventHead;
aeTimeEvent *nearest = NULL;
while(te) {
/*
nearest記錄當前最快到期的節點,初始化爲NULL
1 nearest爲空,把當前節點作爲最小值
2 when_sec小的作爲最小值
3 when_sec一樣的情況下,when_ms小者爲最小值
*/
if (!nearest || te->when_sec < nearest->when_sec ||
(te->when_sec == nearest->when_sec &&
te->when_ms < nearest->when_ms))
nearest = te;
te = te->next;
}
return nearest;
}
最後來看一下事件處理的邏輯,入口函數是
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
該函數由redis初始化時,main函數調用。這個版本使用的多路複用函數是select
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int maxfd = 0, numfd = 0, processed = 0;
fd_set rfds, wfds, efds;
aeFileEvent *fe = eventLoop->fileEventHead;
aeTimeEvent *te;
long long maxId;
AE_NOTUSED(flags);
// 兩種類型的事件都不需要處理
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
// 初始化select對應的結構體,讀,寫,異常三種事件
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
// 處理文件事件
if (flags & AE_FILE_EVENTS) {
while (fe != NULL) {
// 根據需要處理的事件,設置對應的變量對應的位
if (fe->mask & AE_READABLE) FD_SET(fe->fd, &rfds);
if (fe->mask & AE_WRITABLE) FD_SET(fe->fd, &wfds);
if (fe->mask & AE_EXCEPTION) FD_SET(fe->fd, &efds);
// 記錄最大文件描述符select的時候需要用
if (maxfd < fe->fd) maxfd = fe->fd;
// 標記是否有文件事件
numfd++;
fe = fe->next;
}
}
// 有文件事件需要處理,或者有time事件並且沒有設置AE_DONT_WAIT(設置的話就不會進入select定時阻塞)標記
if (numfd || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int retval;
aeTimeEvent *shortest = NULL;
/*
struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
*/
struct timeval tv, *tvp;
// 有time事件需要處理,並且沒有設置AE_DONT_WAIT標記,則select可能會定時阻塞(如果有time節點的話)
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
// 找出最快到期的節點
shortest = aeSearchNearestTimer(eventLoop);
// 有待到期的time節點
if (shortest) {
long now_sec, now_ms;
// 獲取當前時間
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
// 算出相對時間,秒數
tvp->tv_sec = shortest->when_sec - now_sec;
// 不夠,需要借位
if (shortest->when_ms < now_ms) {
// 微秒
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
// 借一位,減一
tvp->tv_sec --;
} else {
// 乘以1000,即微秒
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
} else {
// 沒有到期的time節點
// 設置了AE_DONT_WAIT,則不會阻塞在select
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
// 一直阻塞直到有事件發生
tvp = NULL; /* wait forever */
}
}
retval = select(maxfd+1, &rfds, &wfds, &efds, tvp);
if (retval > 0) {
fe = eventLoop->fileEventHead;
while(fe != NULL) {
int fd = (int) fe->fd;
// 有感興趣的事件發生
if ((fe->mask & AE_READABLE && FD_ISSET(fd, &rfds)) ||
(fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds)) ||
(fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds)))
{
int mask = 0;
// 記錄發生了哪些感興趣的事件
if (fe->mask & AE_READABLE && FD_ISSET(fd, &rfds))
mask |= AE_READABLE;
if (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds))
mask |= AE_WRITABLE;
if (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds))
mask |= AE_EXCEPTION;
// 執行回調
fe->fileProc(eventLoop, fe->fd, fe->clientData, mask);
processed++;
/* After an event is processed our file event list
* may no longer be the same, so what we do
* is to clear the bit for this file descriptor and
* restart again from the head. */
/*
執行完回調後,文件事件隊列可能發生了變化,
重新開始遍歷
*/
fe = eventLoop->fileEventHead;
// 清除該文件描述符
FD_CLR(fd, &rfds);
FD_CLR(fd, &wfds);
FD_CLR(fd, &efds);
} else {
fe = fe->next;
}
}
}
}
// 處理time事件
if (flags & AE_TIME_EVENTS) {
te = eventLoop->timeEventHead;
// 先保存這次需要處理的最大id,防止在time回調了不斷給隊列新增節點,導致死循環
maxId = eventLoop->timeEventNextId-1;
while(te) {
long now_sec, now_ms;
long long id;
// 在本次回調裏新增的節點,跳過
if (te->id > maxId) {
te = te->next;
continue;
}
// 獲取當前時間
aeGetTime(&now_sec, &now_ms);
// 到期了
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
int retval;
id = te->id;
// 執行回調
retval = te->timeProc(eventLoop, id, te->clientData);
// 是否需要繼續註冊事件,是則修改超時時間,否則刪除該節點
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
} else {
aeDeleteTimeEvent(eventLoop, id);
}
te = eventLoop->timeEventHead;
} else {
te = te->next;
}
}
}
// 處理的事件個數
return processed; /* return the number of processed file/time events */
}