FileEvent

在了解整个事件驱动的模型前,有先了解一些定义的事件结构体,事件类型总共2个一个FileEvent,TimeEvent:

 

 
  1. /* File event structure */

  2. /* 文件事件结构体 */

  3. typedef struct aeFileEvent {

  4. //只为读事件或者写事件中的1种

  5. int mask; /* one of AE_(READABLE|WRITABLE) */

  6. //读方法

  7. aeFileProc *rfileProc;

  8. //写方法

  9. aeFileProc *wfileProc;

  10. //客户端数据

  11. void *clientData;

  12. } aeFileEvent;

  13.  
  14. /* Time event structure */

  15. /* 时间事件结构体 */

  16. typedef struct aeTimeEvent {

  17. //时间事件id

  18. long long id; /* time event identifier. */

  19. //时间秒数

  20. long when_sec; /* seconds */

  21. //时间毫秒

  22. long when_ms; /* milliseconds */

  23. //时间事件中的处理函数

  24. aeTimeProc *timeProc;

  25. //被删除的时候将会调用的方法

  26. aeEventFinalizerProc *finalizerProc;

  27. //客户端数据

  28. void *clientData;

  29. //时间结构体内的下一个结构体

  30. struct aeTimeEvent *next;

  31. } aeTimeEvent;

  32.  
  33. /* A fired event */

  34. /* fired结构体,用来表示将要被处理的文件事件 */

  35. typedef struct aeFiredEvent {

  36. //文件描述符id

  37. int fd;

  38. int mask;

  39. } aeFiredEvent;

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

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

 

 
  1. /* State of an event based program */

  2. typedef struct aeEventLoop {

  3. //目前创建的最高的文件描述符

  4. int maxfd; /* highest file descriptor currently registered */

  5. int setsize; /* max number of file descriptors tracked */

  6. //下一个时间事件id

  7. long long timeEventNextId;

  8. time_t lastTime; /* Used to detect system clock skew */

  9. //3种事件类型

  10. aeFileEvent *events; /* Registered events */

  11. aeFiredEvent *fired; /* Fired events */

  12. aeTimeEvent *timeEventHead;

  13. //事件停止标志符

  14. int stop;

  15. //这里存放的是event API的数据,包括epoll,select等事件

  16. void *apidata; /* This is used for polling API specific data */

  17. aeBeforeSleepProc *beforesleep;

  18. } aeEventLoop;

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

 

 
  1. /* Prototypes */

  2. aeEventLoop *aeCreateEventLoop(int setsize); /* 创建aeEventLoop,内部的fileEvent和Fired事件的个数为setSize个 */

  3. void aeDeleteEventLoop(aeEventLoop *eventLoop); /* 删除EventLoop,释放相应的事件所占的空间 */

  4. void aeStop(aeEventLoop *eventLoop); /* 设置eventLoop中的停止属性为1 */

  5. int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,

  6. aeFileProc *proc, void *clientData); /* 在eventLoop中创建文件事件 */

  7. void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); /* 删除文件事件 */

  8. int aeGetFileEvents(aeEventLoop *eventLoop, int fd); //根据文件描述符id,找出文件的属性,是读事件还是写事件

  9. long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,

  10. aeTimeProc *proc, void *clientData,

  11. aeEventFinalizerProc *finalizerProc); /* 在eventLoop中添加时间事件,创建的时间为当前时间加上自己传入的时间 */

  12. int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); //根据时间id,删除时间事件,涉及链表的操作

  13. int aeProcessEvents(aeEventLoop *eventLoop, int flags); /* 处理eventLoop中的所有类型事件 */

  14. int aeWait(int fd, int mask, long long milliseconds); /* 让某事件等待 */

  15. void aeMain(aeEventLoop *eventLoop); /* ae事件执行主程序 */

  16. char *aeGetApiName(void);

  17. void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); /* 每次eventLoop事件执行完后又重新开始执行时调用 */

  18. int aeGetSetSize(aeEventLoop *eventLoop); /* 获取eventLoop的大小 */

  19. int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); /* EventLoop重新调整大小 */

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

 

 
  1. /* ae事件执行主程序 */

  2. void aeMain(aeEventLoop *eventLoop) {

  3. eventLoop->stop = 0;

  4. //如果eventLoop中的stop标志位不为1,就循环处理

  5. while (!eventLoop->stop) {

  6. //每次eventLoop事件执行完后又重新开始执行时调用

  7. if (eventLoop->beforesleep != NULL)

  8. eventLoop->beforesleep(eventLoop);

  9. //while循环处理所有的evetLoop的事件

  10. aeProcessEvents(eventLoop, AE_ALL_EVENTS);

  11. }

  12. }

道理很简单通过,while循环,处理eventLoop中的所有类型事件,截取部分processEvents()代码:

 

 
  1. numevents = aeApiPoll(eventLoop, tvp);

  2. for (j = 0; j < numevents; j++) {

  3. aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];

  4. int mask = eventLoop->fired[j].mask;

  5. int fd = eventLoop->fired[j].fd;

  6. int rfired = 0;

  7.  
  8. /* note the fe->mask & mask & ... code: maybe an already processed

  9. * event removed an element that fired and we still didn't

  10. * processed, so we check if the event is still valid. */

  11. if (fe->mask & mask & AE_READABLE) {

  12. rfired = 1;

  13. //根据掩码计算判断是否为ae读事件,调用时间中的读的处理方法

  14. fe->rfileProc(eventLoop,fd,fe->clientData,mask);

  15. }

  16. if (fe->mask & mask & AE_WRITABLE) {

  17. if (!rfired || fe->wfileProc != fe->rfileProc)

  18. fe->wfileProc(eventLoop,fd,fe->clientData,mask);

  19. }

  20. processed++;

  21. }

  22. }

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

 

 
  1. /* 在eventLoop中添加时间事件,创建的时间为当前时间加上自己传入的时间 */

  2. long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,

  3. aeTimeProc *proc, void *clientData,

  4. aeEventFinalizerProc *finalizerProc)

  5. {

  6. long long id = eventLoop->timeEventNextId++;

  7. aeTimeEvent *te;

  8.  
  9. te = zmalloc(sizeof(*te));

  10. if (te == NULL) return AE_ERR;

  11. te->id = id;

  12. aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);

  13. te->timeProc = proc;

  14. te->finalizerProc = finalizerProc;

  15. te->clientData = clientData;

  16. //新加的变为timeEvent的头部

  17. te->next = eventLoop->timeEventHead;

  18. eventLoop->timeEventHead = te;

  19.  
  20. //返回新创建的时间事件的id

  21. return id;

  22. }

    下面说说如何调用事件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中是如何调用的呢,

 

 
  1. //这里存放的是event API的数据,包括epoll,select等事件

  2. void *apidata; /* This is used for polling API specific data */

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

 

 
  1. typedef struct aeApiState {

  2. int epfd;

  3. struct epoll_event *events;

  4. } aeApiState;

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

 

 
  1. static int aeApiCreate(aeEventLoop *eventLoop)

  2. static int aeApiResize(aeEventLoop *eventLoop, int setsize)

  3. static void aeApiFree(aeEventLoop *eventLoop)

  4. static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)

  5. static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)

  6. static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)

  7. static char *aeApiName(void)

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

 

 
  1. state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */

  2. if (state->epfd == -1) {

  3. zfree(state->events);

  4. zfree(state);

  5. return -1;

  6. }

  7. //最后将state的数据赋值到eventLoop的API data中

  8. eventLoop->apidata = state;

  9. return 0;

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

 

 
  1. static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {

  2. aeApiState *state = eventLoop->apidata;

  3. int retval, numevents = 0;

  4.  
  5. retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,

  6. tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

  7. if (retval > 0) {

  8. .....

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

 

 
  1. static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {

  2. aeApiState *state = eventLoop->apidata;

  3. int retval, j, numevents = 0;

  4.  
  5. memcpy(&state->_rfds,&state->rfds,sizeof(fd_set));

  6. memcpy(&state->_wfds,&state->wfds,sizeof(fd_set));

  7.  
  8. retval = select(eventLoop->maxfd+1,

  9. &state->_rfds,&state->_wfds,NULL,tvp);

  10. ......

最后都是基于state中的事件和eventLoop之间的转化实现操作。传入eventLoop中的信息,传入state的信息,经过内部的处理得出终的事件结果。调用就这么简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值