redis学习笔记(四): ae
redis是基于事件驱动的,相应的实现都在ae.c当中。
其实个人对于“事件驱动”的理解不是那么明显,只能说从它的实现上来看稍微有一些感觉:
先由外部模块注册感兴趣的事件以及callback,在poll返回时判断是否有相应模块感兴趣的事件,如果有的话就调用注册的callback
/* 代码中的注释是File event structure,个人理解就是外部模块感兴趣的内容以及处理方式。目前用到这个结构的包括:网络事件以及unix套接字上的内部通信 */ typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE) */ aeFileProc *rfileProc; aeFileProc *wfileProc; void *clientData; } aeFileEvent; /* 代码中的注释是Time event structure,也就是定时处理的事件,用单链表的形式组织起来。finalizerProc这个成员还不理解有什么作用。目前主进程中应该只有serverCron这一个需要定时处理的事件 */ typedef 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; /* 代码中的注释是A fired event,其实就是在poll返回时,每一个发生的事件 */ typedef struct aeFiredEvent { int fd; /* 发生事件的套接字,目前只有inet和unix */ int mask; /* fd上发生的事件 */ } aeFiredEvent; /* 代码中的注释是State of an event based program,个人理解就是对所有事件的管理结构,整个主进程只有一个 */ typedef struct aeEventLoop { int maxfd; /* 当前最大的fd,目前只有select有用 */ int setsize; /* 这个值就是下面events,fired两个数组的大小 */ long long timeEventNextId; time_t lastTime; /* Used to detect system clock skew */ aeFileEvent *events; /* 外部注册的感兴趣的事件 */ aeFiredEvent *fired; /* poll返回的事件 */ aeTimeEvent *timeEventHead; /* 定时器事件 */ int stop; /* 如果是1就要退出事件处理流程 */ void *apidata; /* This is used for polling API specific data */ aeBeforeSleepProc *beforesleep; /* 进入poll之前需要处理的事情 */ } aeEventLoop;
ae.c里面使用如下的方式来决定系统使用的poll机制:
#ifdef HAVE_EVPORT #include "ae_evport.c" #else #ifdef HAVE_EPOLL #include "ae_epoll.c" #else #ifdef HAVE_KQUEUE #include "ae_kqueue.c" #else #include "ae_select.c" #endif #endif #endif
虽然每个c文件对应的poll机制不同,但都定义了自己的aeApiState以及实现的都是相同的api:
aeApiState,
每种poll机制内部使用的相关结构体,例如:select用到的fdset, epoll用到的epoll_fd以及events数组
aeApiCreate,
poll机制的初始化,每个poll机制都会在这里分配一个新的aeApiState结构,并做一些特定的初始化操作
例如:对于select来说应该是就是初始化fdset,用于select的相关调用;对于epoll来说,需要创建epoll的fd以及epoll使用的events数组
aeApiResize,
调整poll机制中能处理的事件数目,例如:对于select来说,其实只要不超过fdset的最大值(一般系统默认是1024)它就什么都不做,否则返回错误;对于epoll来说,就是重新分配events数组
这个函数只在config阶段会被调用
aeApiFree,
对于select来说,主要就是释放aeApiState的空间
对于epoll来说,主要就是关闭epoll的fd, 释放aeApiState以及events的空间
aeApiAddEvent,
对于select来说,就是往某个fd_set里面增加fd
对于epoll来说,就是在events中增加/修改感兴趣的事件
aeApiDelEvent,
对于select来说,就是从某个fd_set里面删除fd
对于epoll来说,就是在events中删除/修改感兴趣的事件
aeApiPoll,
主要的poll入口,比如select或者epoll_wait
aeApiName
返回poll机制的名字,比如select或者epoll
ae.c里面实现的主流程其实也很简单
void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; while (!eventLoop->stop) { if (eventLoop->beforesleep != NULL) eventLoop->beforesleep(eventLoop); aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP); } }
beforesleep主要是一些(进入poll之前的)准备工作或者是处理上一轮poll中未完成任务的最后一步。后面再仔细看这一部分
aeProcessEvents,第二个参数是AE_ALL_EVENTS,所以在里面会(按顺序)处理file和time两类事件:
调用aeApiPoll时,需要指定超时时间或者死等。自然地,它会从aeTimeEvent的单链表中找出距离当前最近的定时器事件的超时时间,以该时间做为超时时间调用具体的poll函数(select/epoll_wait)。否则,如果没有找到任何超时事件,则会让poll函数进入死等。
不过要注意的是,如果aeProcessEvents的第二个参数指定了AE_DONT_WAIT,那么就不能在poll函数上等,会直接把时间设置为0,也就是具体的poll函数会立刻超时。
aeApiPoll返回之后,处理file事件(如果有的话)。最后,再调用processTimeEvents处理time事件(如果aeProcessEvents的第二个参数指定了AE_TIME_EVENTS标记)
redis使用ae的大致流程如下:
{ ... aeCreateEventLoop /* 创建总的事件管理结构 */ aeCreateTimeEvent /* 创建定时器事件的管理结构 */ aeCreateFileEvent for inet socket /* 创建网络事件的管理结构 */ aeCreateFileEvent for unix socket /* 内部通信事件的管理结构 */ aeSetBeforeSleepProc /* 设置beforeSleep处理函数 */ aeSetAfterSleepProc /* 设置afterSleep处理函数 */ ... while (!stop) { beforeSleep /* 调用beforeSleep处理函数 */ aeApiPoll /* 进入poll函数 */ afterSleep /* 调用afterSleep处理函数 */ process file events /* 处理file events */ process time events /* 处理time events */ } ... }