前言:

redis是一个和memcached相似的kv存储。
作者自己在里边实现了一个小的事件库。
为什么作者要自造轮子呢?作者自己解释如下:

https://groups.google.com/group/redis-db/browse_thread/thread/b52814e9ef15b8d0/

redis的事件库最大的特点就是实现很简洁。

版本:

2.4.16

主要的数据结构:

在Ae.h中就可以看到所有事件处理相关的数据结构和接口。

三种event:

typedef struct aeFileEvent {
        int mask; /* one of AE_(READABLE|WRITABLE) */
       aeFileProc *rfileProc;
       aeFileProc *wfileProc;
       void *clientData;
} aeFileEvent;

aeFileEvent的读写回调函数分开,对用户透明,在aeCreateFileEvent内设置。
读写共享同一份clientData。

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;

id初始化为eventLoop->timeEventNextId++
when_sec和when_ms保存截止时间(当前时间+用户指定的时间)。

typedef struct aeFiredEvent {
       int fd;
       int mask;
} aeFiredEvent;

表示已经发生的fileevent。
详细操作参见aeApiPoll。

以上三个数据结构只被eventloop用于管理,不提供给外界。
重要的eventloop:

typedef struct aeEventLoop {
        int maxfd;
        long long timeEventNextId;
        aeFileEvent events[AE_SETSIZE]; /* Registered events */
        aeFiredEvent fired[AE_SETSIZE]; /* Fired events,事件发生后在这记录 */
        aeTimeEvent *timeEventHead;
        int stop;
        void *apidata; /* This is used for polling API specific data */
        aeBeforeSleepProc *beforesleep;
} aeEventLoop;

注意,eventloop里的events和fired数组,不是指针,而是直接申请了空间。
对于时间event的管理是使用简单的单链表,头指针timeEventHead。

主要的接口:
eventloop操作:

aeEventLoop *aeCreateEventLoop(void);
        分配内存,初始化field。
        调用后端的aeApiCreate。

void aeDeleteEventLoop(aeEventLoop *eventLoop) {
aeApiFree(eventLoop);
zfree(eventLoop);
}
void aeStop(aeEventLoop *eventLoop) {
eventLoop->stop = 1;

}

删除、停止eventLoop就这么简单。

event操作:

event的create和delete操作是以(fd, mask)为标识的,使用者不直接得到event结构。

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,aeFileProc *proc, void *clientData);

直接将信息保存到eventLoop的events[fd]中。
        将(fd, mask)添加到后端。
        更新eventLoop->maxfd。

void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
        从events[fd]的mask中去掉参数mask。
        如果events[fd]的mask变为0了,更新eventLoop->maxfd。
        从后端删除(fd, mask)。
注意:当你添加了(fd, 读|写),后来又删除了(fd, 读)时,(fd, 读)依然有效。

long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,aeTimeProc *proc, void *clientData,aeEventFinalizerProc *finalizerProc);
        分配内存,初始化field。
        添加到eventLoop->timeEventHead的单链表中。
        返回id。

int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id);
        从eventLoop->timeEventHead的单链表中遍历找到id,free掉。

事件循环:

Ae.h里有三个关于事件循环的接口:aeWait,aeMain, aeProcessEvents。

aeWait是对poll的简单包装,用于实现syncRead,syncWrite(两个带超时的safe read/write实现)。
个人感觉aeWait和其他部分关系不大,不应该放在Ae这部分。
不细说,有兴趣的可以自己看看代码。

void aeMain(aeEventLoop *eventLoop) {

eventLoop->stop = 0;
        while (!eventLoop->stop) {
               if (eventLoop->beforesleep != NULL)
                       eventLoop->beforesleep(eventLoop);
                aeProcessEvents(eventLoop, AE_ALL_EVENTS);
        }
}
aeMain由main函数最后调用进入事件循环。看代码即可,也不多说了。

int aeProcessEvents(aeEventLoop *eventLoop, int flags);
aeMain中的一轮,也可以被单独调用,处理发生的fileevent 或timeevent或两者。如果没有设置DONT_WAIT,则会阻塞等到有fileevent fired或者有timeevent到期。

大体流程:
        如果有注册的event 或者 (flags指示要处理timeevent 而且 没有设置DONT_WAIT)
                case:要处理timeevent且没有设置DONT_WAIT,则tvp置为等待最近要发生的       timeevent需要的时间。
               case:不需要处理timeevent且没有DONT_WAIT,tvp = NULL。意味着接下来我们可以尽情阻塞。
                case:设置了DONT_WAIT,tvp设为{0, 0},意味着马上返回不阻塞。
        numevents = aeApiPoll(eventLoop, tvp);
        一个对firedevent的for循环,处理所有发生了的event。
        如果要处理timeevent,processTimeEvents。
        返回处理的event数量。

以下两个是aeProcessEvents调用的函数:
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop);
        找到返回截止时间最近的aeTimeEvent。
        根据不过早优化的原则和timeevent插入时的无序,作者还是使用遍历:遍历单链表,找到最近要发生的timeevent。我很喜欢这种风格!

虽然现在不需要,但给出了两个需要时可以考虑的优化:

1) Insert the event in order, so that the nearest is just the head. Much better but still insertion or deletion of timers is O(N).
2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
(为什么不考虑优先级队列呢?跟skiplist时间复杂度差不多,感觉空间上更省。)

static int processTimeEvents(aeEventLoop *eventLoop);
        从头遍历timeevent队列,执行已到期timeevent的timeProc函数。如果返回AE_NOMORE,就将该timveevent删除。否则返回增加的时间,调整timeevent的截止时间。
        每当处理过timeevent后,都将从头再来。

后端的封装

使用三个后端,分别封装在三个c文件中:

Ae_epoll.c
Ae_kqueue.c
Ae_select.c

优先级从上到下,编译时确定。直接include进Ae.c:

#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
        #ifdef HAVE_KQUEUE
        #include "ae_kqueue.c"
#else
        #include "ae_select.c"
        #endif
#endif

提供六个接口:

aeApiCreate(aeEventLoop *eventLoop);
aeCreateEventLoop时,进行backend特定的初始化。

aeApiFree(aeEventLoop *eventLoop) ;
aeDeleteEventLoop时,进行backend特定的清理。

aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask);
aeCreateFileEvent时,将fd, mask注册到后端,需要监听其状态变化。

aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask);
aeDeleteFileEvent时,将fd, mask从后端注销,不再监听其状态变化。

aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp);
开始监听,将发生改变的event的(fd, mask)复制到对应eventLoop->fired中,从0开始顺着来。
最后,返回发生的事件数。不会返回-1,只会是0或>0。

static char *aeApiName(void) {
return "epoll";
}
这个就不用说什么了。。。

总结:

redis的事件驱动设计的相当简洁。一共就4个c文件,1个h文件。代码也非常少,从看代码到写完这篇总结的初稿也就5个小时左右。非常推荐作为入门来看。

event类型只提供fileevent和timeevent。相关的接口也非常少。

fileevent不直接暴露给用户。
               用户的create, delete操作通过(fd, mask)标识。内部使用firedevent来记录已经发生的事件。

所需的数据结构直接以数组的形式分配在eventloop中。

timeevent设计更是简单,同样不直接暴露给用户。
               没有与fileevent结合,只是单纯的表示一个定时事件。如果需要,在timeevent的回调函数内部可以进行fileevent的create操作。
               时间戳简单使用了gettimeofday+过期时间获得。maxid只是用来避免处理timeevents时的循环。
               timeevent使用单链表管理,插入操作均在链表头。当需要某些查找操作时,只有遍历。

eventloop一共也只有8个field。事件循环也不过百行。。。

但是毕竟不是通用的事件驱动库,与libevent, libev相比它很多地方做的不好,比如跨平台,多线程支持,事件类型过少,性能还有提升空间等。不过已经完全满足了redis的需要。

如果有时间,我也想仿照这个思路自己写一个事件驱动库,替换掉evhttpd中的libevent。

本来想在最后画张图来表示event和eventloop的关系,以及event的状态转移。可当在纸上画完草稿后,感觉还是不画的好。因为我觉着看图太复杂了,不如直接看代码。

同样,如果想更好了解redis/ae的话,忘掉这篇文章,留出两三个小时去看代码吧

redis/ae总结相关推荐

  1. redis/ae总结 .

    http://blog.csdn.net/tricky1997/article/details/7905729 前言: redis是一个和memcached相似的kv存储. 作者自己在里边实现了一个小 ...

  2. 基于redis AE异步网络架构

    最近的研究已redis源代码,redis高效率是令人钦佩. 在我们的linux那个机器,cpu型号, Intel(R) Pentium(R) CPU G630 @ 2.70GHz  Intel(R) ...

  3. 计算机科学精彩帖子收集

    linux源码 LXR 源自"the Linux Cross Referencer",中间的"X"形象地代表了"Cross".与 Sourc ...

  4. redis 之 ae 模型测试

    为什么80%的码农都做不了架构师?>>>    主要用来测试和学习 redis 的 ae 模型 总体来说 代码结构简单,容易理解. 1. 从 redis 目录拷贝 ae.c  ae. ...

  5. 事件库之Redis自己的事件模型-ae

    2019独角兽企业重金招聘Python工程师标准>>> #Redis自己的事件模型 ae ##1.Redis的事件模型库 大家到网上Google"Redis libeven ...

  6. Go 学习笔记(60)— Go 第三方库之 go-redis(初始化 redis、操作 string、操作 list、操作 set、操作 hset)

    1. 第三方库 go-redis 因为 Go 标准库中是没提供 redis 的库,所以我们选择用 go-redis 这个第三方库.源码地址为 https://github.com/go-redis/r ...

  7. python的redis数据库连接与使用

    Redis redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorte ...

  8. Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy

    Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度 ...

  9. Redis安装与调试

     Redis安装与调试 Redis安装与调试linux版本:64位CentOS 6.5 Redis版本:2.8.17  (更新到2014年10月31日) Redis官网:http://redis. ...

最新文章

  1. 从零入门 FreeRTOS 操作系统之创建任务流程
  2. R 语言学习过程全记录 ~
  3. Oracle拆分字符串函数与执行调用
  4. java如何造假数据_JAVA时间数据造假
  5. Game of Swapping Numbers
  6. Java自引用造成的死循环
  7. 计算机表格怎么加减乘除,如何在Word表格中进行加减乘除
  8. centos 多台 文件夹同步_win10+OneDrive,同步备份文件最佳搭档,这样关闭自动备份通知...
  9. 《计算机网络》谢希仁第七版课后答案完整版
  10. Word 公式排版(使用制表符)
  11. 报错笔记-[error] 1615#0: *14 open() “/usr/local/nginx/html/course/zk.jpg“ failed (2: No such file or dir
  12. 微信公众号 微信服务号如何实现服务号点击消息跳转进入小程序
  13. 罗杰斯的创新扩散模型
  14. 1核2g1m服务器能支持多少人在线访问?
  15. 关于css设置第n个元素
  16. 全球与中国小龙虾市场深度研究分析报告
  17. 【Java开发者专场】阿里专家梁笑:2018双十一下单成功率99.9%!供应链服务平台如何迎接大促...
  18. travisscott多高_如何评价说唱歌手Travis Scott ?
  19. app小程序手机端Python爬虫实战01-Uiautomator2自动化抓取工具介绍
  20. Utopia unlimited: reassessing American literary utopias【翻译】

热门文章

  1. chrome查看实际域名_域名实际要多少钱? (专家回答)
  2. 《我是程序员》js方法套用
  3. vb python excel_震惊!当Python遇到Excel后,将开启你的认知虫洞
  4. 进程的通信 - 剪切板
  5. 专线网络故障排查本地网络故障排查
  6. 我的leetcode之旅--万事开头难
  7. 集成电路layout设计的与candence讲义
  8. 华硕 ASUS 笔记本 K42DR 拆机 清灰 教程 图文详解
  9. RGBA 转 RGB
  10. 魅族手机使用鸿蒙系统,魅族宣布接入鸿蒙是怎么回事?魅族手机可以刷鸿蒙系统吗?...