事件驱动的术语出现更频繁。听起来非常大的,今天我把Redis内部驱动器模型来研究它,奖励的感觉啊。一个ae.c主程序,加4文件的事件类型,让你彻底弄清楚,Redis是怎样处理这些事件的。

在Redis的事件处理中。用到了epoll,select,kqueue和evport,evport可能大家会陌生很多。前面3个都是很常见的事件,在libevent的事件网络库中也都有出现。

作者在写这个事件驱动模型的时候。也说了,这仅仅是为了简单的复用了,设计的一个小小的处理模型:

/* A simple event-driven programming library. Originally I wrote this code
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
* it in form of a library for easy reuse.
*
* ae是作者写的一个简单的事件驱动库。后面进行了转化,变得更为简单的复用

所以不是非常复杂。在了解整个事件驱动的模型前,有先了解一些定义的事件结构体,事件类型总共2个一个FileEvent,TimeEvent:

/* File event structure */
/* 文件事件结构体 */
typedef struct aeFileEvent {
//仅仅为读事件或者写事件中的1种
int mask; /* one of AE_(READABLE|WRITABLE) */
//读方法
aeFileProc *rfileProc;
//写方法
aeFileProc *wfileProc;
//客户端数据
void *clientData;
} aeFileEvent; /* Time event structure */
/* 时间事件结构体 */
typedef struct aeTimeEvent {
//时间事件id
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 */
/* fired结构体。用来表示将要被处理的文件事件 */
typedef struct aeFiredEvent {
//文件描写叙述符id
int fd;
int mask;
} aeFiredEvent;

FireEvent仅仅是用来标记要处理的文件Event。

这些事件都存在于一个aeEventLoop的结构体内:

/* State of an event based program */
typedef struct aeEventLoop {
//眼下创建的最高的文件描写叙述符
int maxfd; /* highest file descriptor currently registered */
int setsize; /* max number of file descriptors tracked */
//下一个时间事件id
long long timeEventNextId;
time_t lastTime; /* Used to detect system clock skew */
//3种事件类型
aeFileEvent *events; /* Registered events */
aeFiredEvent *fired; /* Fired events */
aeTimeEvent *timeEventHead;
//事件停止标志符
int stop;
//这里存放的是event API的数据。包含epoll,select等事件
void *apidata; /* This is used for polling API specific data */
aeBeforeSleepProc *beforesleep;
} aeEventLoop;

在每种事件内部,都有定义对应的处理函数,把函数当做变量一样存在结构体中。以下看下ae.c中的一些API的组成:

/* Prototypes */
aeEventLoop *aeCreateEventLoop(int setsize); /* 创建aeEventLoop。内部的fileEvent和Fired事件的个数为setSize个 */
void aeDeleteEventLoop(aeEventLoop *eventLoop); /* 删除EventLoop。释放对应的事件所占的空间 */
void aeStop(aeEventLoop *eventLoop); /* 设置eventLoop中的停止属性为1 */
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData); /* 在eventLoop中创建文件事件 */
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); /* 删除文件事件 */
int aeGetFileEvents(aeEventLoop *eventLoop, int fd); //依据文件描写叙述符id,找出文件的属性,是读事件还是写事件
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc); /* 在eventLoop中加入时间事件,创建的时间为当前时间加上自己传入的时间 */
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); //依据时间id。删除时间事件。涉及链表的操作
int aeProcessEvents(aeEventLoop *eventLoop, int flags); /* 处理eventLoop中的全部类型事件 */
int aeWait(int fd, int mask, long long milliseconds); /* 让某事件等待 */
void aeMain(aeEventLoop *eventLoop); /* ae事件运行主程序 */
char *aeGetApiName(void);
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); /* 每次eventLoop事件运行完后又又一次開始运行时调用 */
int aeGetSetSize(aeEventLoop *eventLoop); /* 获取eventLoop的大小 */
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); /* EventLoop又一次调整大小 */

无非涉及一些文件。时间事件的加入,改动等,都是在eventLoop内部的改动,我们来看下最主要,最核心的方法:

/* ae事件运行主程序 */
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
//假设eventLoop中的stop标志位不为1,就循环处理
while (!eventLoop->stop) {
//每次eventLoop事件运行完后又又一次開始运行时调用
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
//while循环处理全部的evetLoop的事件
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}

道理非常easy通过,while循环,处理eventLoop中的全部类型事件,截取部分processEvents()代码:

 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;
//依据掩码计算推断是否为ae读事件。调用时间中的读的处理方法
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++;
}
}

ae中创建时间事件都是以当前时间为基准创建的。

/* 在eventLoop中加入时间事件。创建的时间为当前时间加上自己传入的时间 */
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
long long id = eventLoop->timeEventNextId++;
aeTimeEvent *te; te = zmalloc(sizeof(*te));
if (te == NULL) return AE_ERR;
te->id = id;
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
//新加的变为timeEvent的头部
te->next = eventLoop->timeEventHead;
eventLoop->timeEventHead = te; //返回新创建的时间事件的id
return id;
}

以下说说怎样调用事件API库里的方法呢。首先隆重介绍什么是epoll,poll。select。kqueu和evport。这些都是一种事件模型。

select事件的模型

(1)创建所关注的事件的描写叙述符集合(fd_set),对于一个描写叙述符。能够关注其上面的读(read)、写(write)、异常(exception)事件,所以通常,要创建三个fd_set。 一个用来收集关注读事件的描写叙述符。一个用来收集关注写事件的描写叙述符。另外一个用来收集关注 异常事件的描写叙述符集合。

(2)轮询全部fd_set中的每个fd ,检查是否有对应的事件发生。假设有,就进行处理。

   

 poll和上面的差别是能够复用文件描写叙述符,上面对一个文件须要轮询3个文件描写叙述符集合。而poll仅仅须要一个,效率更高

epoll是poll的升级版本号,把描写叙述符列表交给内核,一旦有事件发生,内核把发生事件的描写叙述符列表通知给进程,这样就避免了轮询整个描写叙述符列表。

效率极大提高

evport这个出现的比較少,大致意思是evport将某一个对象的特定 event 与 Event port 相关联:

在了解了3种事件模型的原理之后,我们看看ae.c在Redis中是怎样调用的呢,

//这里存放的是event API的数据,包含epoll,select等事件
void *apidata; /* This is used for polling API specific data */

就是上面这个属性。在上面的4种事件中,分别相应着3个文件,分别为ae_poll.c,ae_select.c,可是他们的API结构是类似的。我举当中一个样例,epoll的样例,首先都会有此事件特定的结构体:

typedef struct aeApiState {
int epfd;
struct epoll_event *events;
} aeApiState;

还有共同套路的模板方法:

static int aeApiCreate(aeEventLoop *eventLoop)
static int aeApiResize(aeEventLoop *eventLoop, int setsize)
static void aeApiFree(aeEventLoop *eventLoop)
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)
static char *aeApiName(void)

在创建的时候赋值到eventloop的API data里面去:

 state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
if (state->epfd == -1) {
zfree(state->events);
zfree(state);
return -1;
}
//最后将state的数据赋值到eventLoop的API data中
eventLoop->apidata = state;
return 0;

在取出事件的poll方法的时候是这些方法的一个区分点:

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata;
int retval, numevents = 0; retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
if (retval > 0) {
.....

而在select中的poll方法是这种:

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata;
int retval, j, numevents = 0; memcpy(&state->_rfds,&state->rfds,sizeof(fd_set));
memcpy(&state->_wfds,&state->wfds,sizeof(fd_set)); retval = select(eventLoop->maxfd+1,
&state->_rfds,&state->_wfds,NULL,tvp);
......

最后都是基于state中的事件和eventLoop之间的转化实现操作。

传入eventLoop中的信息。传入state的信息。内部得到处理结果最后一场比赛后,。

就这么简单的调用。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

Redis源代码分析(二十)--- ae事件驱动的更多相关文章

  1. Redis源代码分析(十二)--- redis-check-dump本地数据库检測

    这个文件我在今天分析学习的时候,一直有种似懂非懂的感觉,代码量700+的代码,最后开放给系统的就是一个process()方法.这里说的说的数据库检測,是针对key的检測,会用到,以下提到的结构体: / ...

  2. Redis源代码分析(十)--- testhelp.h小测试框架和redis-check-aof.c 日志检测

    周期分析struct结构体redis代码.最后,越多越发现很多的代码其实大同小异.于struct有袋1,2不分析文件,关于set集合的一些东西,就放在下次分析好了,在选择下个分析的对象时,我考虑了一下 ...

  3. redis 源代码分析(一) 内存管理

    一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中 ...

  4. Redis源代码分析(一)--Redis结构解析

    从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...

  5. 【原创】kafka server源代码分析(二)

    十四.AbstractFetcherManager.scala 该scala定义了两个case类和一个抽象类.两个case类很简单: 1. BrokerAndFectherId:封装了一个broker ...

  6. Redis源代码分析(23)--- CRC循环冗余算法RAND随机数的算法

    他今天就开始学习Redis源代码的一些工具来实现,在任何一种语言工具.算法实现的原理应该是相同的,一些比較经典的算法.比方说我今天看的Crc循环冗余校验算法和rand随机数产生算法. CRC算法全称循 ...

  7. Redis源代码分析(十一年)--- memtest内存测试

    今天,我们继续redis源代码test下测试在封装中的其它文件.今天读数memtest档,翻译了,那是,memory test 存储器测试工具..可是里面的提及了非常多东西,也给我涨了非常多见识,网上 ...

  8. Android 中View的绘制机制源代码分析 二

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要解说 ...

  9. Redis源代码分析(二十八)--- object创建和释放redisObject物

    今天的学习更有效率.该Rio分析过,学习之间的另一种方式RedisObject文件,只想说RedisObject有些生成和转换.都是很类似的.列出里面长长的API列表: /* ------------ ...

随机推荐

  1. Intel baytrail-t support Linux?

    点击这里查看文章 有空试试---唉... Ubuntu (Linux) on Atom Z3700 Series ASUS Transformer Book T100 is particularly ...

  2. 静态方法使用bean

    java类中的代码 public class BidMsgUtil { private static Logger log = Logger.getLogger(BidMsgUtil.class); ...

  3. vs2015 安装问题汇总

    1. The product version that you are trying to set up is earlier than the version already installed o ...

  4. Codeforces Round #253 (Div. 1) B. Andrey and Problem

    B. Andrey and Problem time limit per test 2 seconds memory limit per test 256 megabytes input standa ...

  5. 每天进步一点点——再次了解Linux进程ID

    转载请注明出处:http://blog.csdn.net/cywosp/article/details/38968011 1. 概述 众所周知,进程(process)是一个可运行程序的实例,可是在Li ...

  6. hidden change事件

    原文:hidden change事件 对于隐藏域hidden无法触发onchange的解决方法:在更改此隐藏域的时候,调用下它的onchange方法,使用jquery的话, 就直接加上 $(" ...

  7. linux下安装oracle11g 64位最简客户端(转)

    安装环境 Linux服务器:SuSe11 sp1 64位           Oracle客户端:Oracle11gR2 64位(最简客户端) 部署流程 1.准备工作,首先在oracle官网下载最新的 ...

  8. ueditor问题解决

    ueditor图片无法上传? 解决: imageUp.ashx 去掉这一行 <%@ Assembly Src="Uploader.cs" %> 参考: http://w ...

  9. 制作简易计算器处理过程Servlet

    CalculationServlet.java: package com.you.servlet; import java.io.IOException; import java.io.PrintWr ...

  10. hdu 神、上帝以及老天爷

    HDU 2006'10 ACM contest的颁奖晚会隆重开始了! 为了活跃气氛,组织者举行了一个别开生面.奖品丰厚的抽奖活动,这个活动的具体要求是这样的: 首先,所有参加晚会的人员都将一张写有自己 ...