Redis 事件驱动使用了自带的ae库,源代码见ae.c,只有400多行,实现简单,效率很高。下面对这段ae事件驱动源代码进行简单分析下,本人新手,第一次写源码分析,分析有误的地方或者表述不清的地方望指出,我再改正下。
aeEventLoop *aeCreateEventLoop(int setsize) {
aeEventLoop *eventLoop;
int i;
if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
eventLoop->setsize = setsize;
eventLoop->lastTime = time(NULL);
eventLoop->timeEventHead = NULL;
eventLoop->timeEventNextId = 0;
eventLoop->stop = 0;
eventLoop->maxfd = -1;
eventLoop->beforesleep = NULL;
if (aeApiCreate(eventLoop) == -1) goto err;
/* Events with mask == AE_NONE are not set. So let's initialize the
* vector with it. */
for (i = 0; i < setsize; i++)
eventLoop->events[i].mask = AE_NONE;
return eventLoop;
err:
if (eventLoop) {
zfree(eventLoop->events);
zfree(eventLoop->fired);
zfree(eventLoop);
}
return NULL;
aeEventLoop是一个记录事件状态的结构,aeCreateEventLoop创建一个这样的结构,分配内存空间,初始化events,fired等参数。需要分配内存空间的有三个指针,第一个是eventLoop,这是结构指针;另两个是events和fired,events是注册事件,fired是解除事件。这三个指针内存空间任何一个分配失败的话,均跳转到err处理----释放已分配的空间并返回NULL。若分配成功,则设置其他参数初始值,如setsize(最大文件描述符数量值),这个值使用aeCreateEventLoop给定的参数。最后一步初始化注册事件掩码(即事件类型值),用一个for循环对setsize个事件设定默认事件类型。这里的事件mask使用了AE_NONE,其值在ae.h中有预定义,ae库有三种事件类型:AE_NONE(0), AE_READABLE(1),AE_WRITABLE(2).(括号内为事件类型值)。
此外,这里还有个值得注意的对象,stop。这是一个循环处理的一个标志(eventLoop会不停的调用aeProcessEvents),初始值为0,表示处理会一直执行直到stop标志变为1。
下面的两个函数aeGetSetSize及aeResizeSetSize,分别是返回事件setsize大小与修改setsize大小。
/* Return the current set size. */
int aeGetSetSize(aeEventLoop *eventLoop) {
return eventLoop->setsize;
}
/* Resize the maximum set size of the event loop.
* If the requested set size is smaller than the current set size, but
* there is already a file descriptor in use that is >= the requested
* set size minus one, AE_ERR is returned and the operation is not
* performed at all.
*
* Otherwise AE_OK is returned and the operation is successful. */
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) {
int i;
if (setsize == eventLoop->setsize) return AE_OK;
if (eventLoop->maxfd >= setsize) return AE_ERR;
if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR;
eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize);
eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize);
eventLoop->setsize = setsize;
/* Make sure that if we created new slots, they are initialized with
* an AE_NONE mask. */
for (i = eventLoop->maxfd+1; i < setsize; i++)
eventLoop->events[i].mask = AE_NONE;
return AE_OK;
}
aeResizeSetSize函数中,要修改一个指定事件的setsize值,要先判断要修改的值是否合法。若修改值与原值一样,直接返回AE_OK;若修改值比事件里当前已注册的文件描述符个数少,则报错,返回AE_ERR;若aeApiResize返回值不为0,同样返回AE_ERR视为无效修改值。确定修改值合法之后,则修改之前已分配的内存空间,在原来的基础上增加分配空间并初始化增加的事件类型值。之前有maxfd个,新增加的个数为setsize-maxfd,初始化后面增加的即可,最后返回AE_OK。
删除及停止一个事件:aeDeletDEventLoop , aeStop
void aeDeleteEventLoop(aeEventLoop *eventLoop) {
aeApiFree(eventLoop);
zfree(eventLoop->events);
zfree(eventLoop->fired);
zfree(eventLoop);
}
void aeStop(aeEventLoop *eventLoop) {
eventLoop->stop = 1;
}
删除事件前,首先释放事件api占用的空间,然后再释放events及fired空间,最后删除这个事件指针内存空间。 终止事件时,只需将事件状态参数值stop设为1即可。
时间事件处理函数ProcessTimeEvent,该函数返回一共处理了多少时间事件数。
/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
aeTimeEvent *te;
long long maxId;
time_t now = time(NULL);
/* If the system clock is moved to the future, and then set back to the
* right value, time events may be delayed in a random way. Often this
* means that scheduled operations will not be performed soon enough.
*
* Here we try to detect system clock skews, and force all the time
* events to be processed ASAP when this happens: the idea is that
* processing events earlier is less dangerous than delaying them
* indefinitely, and practice suggests it is. */
if (now < eventLoop->lastTime) {
te = eventLoop->timeEventHead;
while(te) {
te->when_sec = 0;
te = te->next;
}
}
eventLoop->lastTime = now;
te = eventLoop->timeEventHead;
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);
processed++;
/* After an event is processed our time event list may
* no longer be the same, so we restart from head.
* Still we make sure to don't process events registered
* by event handlers itself in order to don't loop forever.
* To do so we saved the max ID we want to handle.
*
* FUTURE OPTIMIZATIONS:
* Note that this is NOT great algorithmically. Redis uses
* a single time event so it's not a problem but the right
* way to do this is to add the new elements on head, and
* to flag deleted elements in a special way for later
* deletion (putting references to the nodes to delete into
* another linked list). */
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;
}
时间事件触发受系统时钟影响,若将系统时间改变然后再重新设置到正确的点,时间事件处理可能会发生难以预知的延迟,即有些事件可能已经过期而另一些可能会延迟。这里时间事件考虑了这种情况,在进行处理前首先判断当前系统时间值是否出现问题。
time_t now = time(NULL);
/*....*/
if (now < eventLoop->lastTime) {
te = eventLoop->timeEventHead;
while(te) {
te->when_sec = 0;
te = te->next;
}
}
eventLoop->lastTime = now;
lastTime对象定义在aeEventLoop中,用来检测系统时钟故障的对象。
typedef struct aeEventLoop {
int maxfd; /* highest file descriptor currently registered */
/*.....*/
time_t lastTime; /* Used to detect system clock skew */
/*.......*/
} aeEventLoop;
若系统当前时间now已变为比EventLoop里的当前系统时间(lastTime)小,即所有事件均已超时过期,将重置时间事件列表里所有时间值(设为0)。否则,不管lastTime值比now大还是相同,都使用新的当前系统时间作为lastTime的值。
之后,寻找事件id合法的(即te->id<=maxId)的对象,对这些对象的when_sec值比当前时间的now_sec小,即为需要处理的时间事件对象,调用timeProc函数处理之,处理之后使processed(计数器)的值加1.这里需要注意的一个地方就是,每次处理了一个时间事件之后,为了防止之前的队列发生改变,就要重新回到队列头开始下一轮处理。所以在上面的计数值更新之后,要重置te指针位置,让它重新指向队列头。
te = eventLoop->timeEventHead;
aeProcessEvents函数,首先处理时间事件然后是文件事件。函数有有个flags参数标示,如果没有特殊值的话这个处理函数将陷入睡眠直到文件事件解除或者时间事件发生。该函数的返回值是处理的事件数。先附上这段处理函数的所有说明及代码,然后再简单分析几个需要注意的地方。
/* Process every pending time event, then every pending file event
* (that may be registered by time event callbacks just processed).
* Without special flags the function sleeps until some file event
* fires, or when the next time event occurs (if any).
*
* If flags is 0, the function does nothing and returns.
* if flags has AE_ALL_EVENTS set, all the kind of events are processed.
* if flags has AE_FILE_EVENTS set, file events are processed.
* if flags has AE_TIME_EVENTS set, time events are processed.
* if flags has AE_DONT_WAIT set the function returns ASAP until all
* the events that's possible to process without to wait are processed.
*
* The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents;
/* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
/* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
long now_sec, now_ms;
/* Calculate the time missing for the nearest
* timer to fire. */
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 {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
if (tvp->tv_sec < 0) tvp->tv_sec = 0;
if (tvp->tv_usec < 0) tvp->tv_usec = 0;
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout
* to zero */
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
}
numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int rfired = 0;
/* note the fe->mask & mask & ... code: maybe an already processed
* event removed an element that fired and we still didn't
* processed, so we check if the event is still valid. */
if (fe->mask & mask & AE_READABLE) {
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
}
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
}
processed++;
}
}
/* Check time events */
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}
在上面这段代码中,timeval tv,*tvp定义均在if语句内部,个人感觉定义放在if外部更合理一点。因为若是if条件未满足的话,else条件中的tv和tvp变量均会变成未定义,会出错。当然实际中可能一般都进不去那个else分支内。