学习和整理了前面两篇的知识,现在开始进入这次的重点redis的ae源码了。
redis自己封装的ae事件驱动的主体结构
/* State of an event based program */
typedef struct aeEventLoop {
int maxfd; /* highest file descriptor currently registered */
int setsize; /* max number of file descriptors tracked */
long long timeEventNextId;
time_t lastTime; /* Used to detect system clock skew */
aeFileEvent *events; /* Registered events */
aeFiredEvent *fired; /* Fired events */
aeTimeEvent *timeEventHead;
int stop;
void *apidata; /* This is used for polling API specific data */
aeBeforeSleepProc *beforesleep;
} aeEventLoop;
这里我们重点关注,aeFileEvent *events 这个文件事件
/* File event structure */
typedef struct aeFileEvent {
int mask; //用来记录注册的事件(struct epoll.events)
aeFileProc *rfileProc; //读操作回调
aeFileProc *wfileProc; //写操作回调
void *clientData; //client的数据
} aeFileEvent;
接下来注册一个文件事件
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
//用文件描述符作为下标存储在eventLoop->events中
aeFileEvent *fe = &eventLoop->events[fd];
//这里的aeApiAddEvent 就是用epoll_ctl注册这个文件描述的事件监听
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
return AE_ERR;
fe->mask |= mask;
//根据mask设置回调函数是读还是写
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
fe->clientData = clientData;
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
这样就注册了一个文件的事件了。接下来redis用一个aeMain做一个循环一直监听和处理事件
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
/**
.
.
省略部分代码
.
.
**/
//aeApiPoll 调用epoll_wait回去事件
int processed = 0, numevents;
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;
//根据mask判断执行哪个回调
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++;
}
}
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
这样一个事件处理的创建到处理的方法大致的都在这里了。一些细节还是要继续看源代码。
redis除了文件事件还有时间时间,redis用单链表存储,每次遍历查看到时间的事件然后触发,看到了很多博客都说可以用最小堆之类的数据结构优化,其实是没有必要的,可以全局搜索aeCreateTimeEvent(注册时间事件的方法),只在redis.c的initServer 初始化redis serverCron 和 redis-benchmark.c中,总共有两次,也就是说redis server的timeEvent 也就是个位数的数量级,没看错的就是1个,所以根本没有必要做优化。