int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);
等待事件是否就绪,类似于Java NIO中 select 方法。如果事件就绪,将就绪的event存入events数组中。
参数 epfd:epoll实例的文件描述符; events:已就绪的事件数组; intmaxevents:每次能处理的事件数; timeout:阻塞时间,等待产生就绪事件的超时值。
源码分析
事件
Redis事件系统中将事件分为两种类型:
文件事件;网络套接字对应的事件; 时间事件:Redis中一些定时操作事件,例如 serverCron 函数。下面从事件的注册、触发两个流程对源码进行分析
绑定事件
建立 eventLoop
在 initServer方法(由 redis.c 的 main 函数调用) 中,在建立 RedisDb 对象的同时,会初始化一个“eventLoop”对象,我称之为事件处理器对象。结构体的关键成员变量如下所示:
初始化 eventLoop 在 ae.c 的“aeCreateEventLoop”方法中执行。该方法中除了初始化 eventLoop 还调用如下方法初始化了一个 epoll 实例。
也正是在此处调用了系统方法“epoll_create”。这里的state是一个aeApiState结构,如下所示:
这个 state 由 eventLoop->apidata 来记录。
绑定ip端口与句柄
通过 listenToPort 方法开启TCP端口,每个IP端口会对应一个文件描述符 ipfd(因为服务器可能会有多个ip地址)
注意:*eventLoop 和 ipfd 分别被 server.el 和 server.ipfd[] 引用。server 是结构体 RedisServer 的实例,是Redis的全局变量。
注册事件
如下所示代码,为每一个文件描述符绑定一个事件函数
aeCreateFileEvent 函数中有一个方法调用:aeApiAddEvent,代码如下
这里实际上就是调用系统方法“epoll_ctl”,将事件(文件描述符)注册进 epoll 中。首先要封装一个 epoll_event 结构,即 ee ,通过“epoll_ctl”将其注册进 epoll 中。
除此之外,aeCreateFileEvent 还完成了下面两个重要操作:
将事件函数“acceptTcpHandler”存入了eventLoop中,即由eventLoop->events[fd]->rfileProc 来引用(也可能是wfileProc,分别代表读事件和写事件); 将当操作码添加进 eventLoop->events[fd]->mask 中(mask 类似于JavaNIO中的ops操作码,代表事件类型)。事件监听与执行
redis.c 的main函数会调用 ae.c 中的 main 方法,如下所示:
上述代码会调用 aeProcessEvents 方法用于处理事件,方法如下所示
该函数中代码大致分为三个主要步骤
根据时间事件与当前时间的关系,决定阻塞时间 tvp; 调用aeApiPoll方法,将就绪事件都写入eventLoop->fired[]中,返回就绪事件数目; 遍历eventLoop->fired[],遍历每一个就绪事件,执行之前绑定好的方法rfileProc 或者wfileProc。ae_epoll.c 中的 aeApiPoll 方法如下所示:
执行epoll_wait后,就绪的事件会被写入 eventLoop->apidata->events 事件槽。后面的循环就是将事件槽中的事件写入到 eventLoop->fired[] 中。具体描述:每一个事件都是一个 epoll_event 结构,用e来指代,则e.data.fd代表文件描述符,e->events表示其操作码,将操作码转化为mask,最后将fd 和 mask 都写入eventLoop->fired[j]中。
之后,在外层的 aeProcessEvents 方法中会执行函数指针 rfileProc 或者 wfileProc 指向的方法,例如前文提到已注册的“acceptTcpHandler”。
总结
Redis的网络模块其实是一个简易的Reactor模式。本文顺着“服务端注册事件——>接受客户端连接——>监听事件是否就绪——>执行事件”这样的路线,来分析Redis源码,描述了Redis接受客户端connect的过程。实际上NIO的思想都基本类似。
到此这篇关于Redis网络模型的源码详析的文章就介绍到这了,更多相关Redis网络模型源码内容请搜索七叶笔记以前的文章或继续浏览下面的相关文章希望大家以后多多支持七叶笔记!