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 */
    }
    ...
}
原文地址:https://www.cnblogs.com/flypighhblog/p/7748514.html