每一个cs程序尤为是高并发的网络服务端程序都有本身的网络异步事件处理库,redis不例外。html
事件库仅仅包括ae.c、ae.h,还有3个不一样的多路复用(本文仅描述epoll)的wrapper文件,事件库封装了框架调用的主循环函数,暴露了时间、文件事件注册和销毁函数,典型的依赖反转模式。
网络操做都在networking.c里,封装了常见的socket操做。mysql
咱们从redis启动的main函数开始,从用户发出链接键入命令开始遍历网络事件库所涉及的函数,unix套接口相关函数不表。redis
首先对几个最经常使用对象进行解释。sql
//redis启动的时候(init_server())会建立的一个全局使用的事件循环结构 typedef struct aeEventLoop { int maxfd; //仅仅是select 使用 long long timeEventNextId; aeFileEvent events[AE_SETSIZE]; //用于保存epoll须要关注的文件事件的fd、触发条件、注册函数。 aeFiredEvent fired[AE_SETSIZE]; //epoll_wait以后得到可读或者可写的fd数组,经过aeFiredEvent->fd再定位到events。 aeTimeEvent *timeEventHead; //以链表形式保存多个时间事件,每隔一段时间机会触发注册的函数。 int stop; void *apidata; //每种多路复用方法的使用的私有变量,例如epoll就是epfd和一个事件数组;而select是保存rset、wset。 aeBeforeSleepProc *beforesleep; // sleep以前调用的函数,有些事情是每次循环必须作的,并不是文件、时间事件。 } aeEventLoop; //文件可读写事件 typedef struct aeFileEvent { int mask; //触发条件:读、写 aeFileProc *rfileProc; //当fd可读时执行的事件(accept,read) aeFileProc *wfileProc; //当fd可写时执行的事件(write) void *clientData; //caller 传入的数据指针 } aeFileEvent; //超时时间事件 typedef struct aeTimeEvent { long long id; /* time event identifier. */ long when_sec; /* seconds */ long when_ms; /* milliseconds */ aeTimeProc *timeProc; //当出现超时的时候所执行的事件 aeEventFinalizerProc *finalizerProc; void *clientData; //caller传入的数据指针 struct aeTimeEvent *next; //单链表指向下一个时间事件 } aeTimeEvent; //从epoll_wait或者select返回的,已经触发的文件事件 typedef struct aeFiredEvent { int fd; int mask; } aeFiredEvent;
咱们来模拟redis server 启动和用户键入命令的过程,先上图。api
好吧,从main函数开始吧。数组
// src/redis.c 853 server.el = aeCreateEventLoop();
建立一个aeEventLoop结构体,成员apidata指向一个aeApiState对象,若是使用epoll,fd是由epoll_create建立的全局epoll fd ,events[] 用于保存epoll_wait返回的事件组。网络
// src/redis.c 866 server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);
建立监听。并发
// src/redis.c 903 aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
注册一个时间事件serverCron,做用之后再讲。app
// src/redis.c 906 aeCreateFileEvent(server.el,server.ipfd,AE_READABLE, acceptTcpHandler,NULL)
为监听fd的注册一个文件事件,首先把listenfd和触发条件epoll_ctl加入到全局的epoll fd进行监控。再以fd做为文件事件event数组index定位,mask填入只读,rfileProc填入acceptTcpHandler函数。框架
// src/redis.c 1571 aeSetBeforeSleepProc(server.el,beforeSleep);
aeEventLoop注册一个beforeSleep函数,这个函数在主循环里每次会被调用,做用之后再讲。
// src/ redis.c 1572 aeMain()
网络事件库的核心循环函数。
// src/ae.c 379 aeCreateLoop->beforesleep()
就是上面注册的beforeSleep函数。
// src/ae.c 275 aeProcessEvents(eventLoop, AE_ALL_EVENTS);
先调度aeApiPoll,用epoll_wait处理文件事件,返回的fd和触发条件先存储在eventLoop->fired[]里,而后根据fired[]每一个事件的的fd,定位到events,根据触发条件调用已经注册的事件。
文件事件处理完毕后,接下来就是处理超时时间事件,这里不表。
假若有个用户链接上redis,则从redis servere就从上面的aeApiPoll的poll_wait返回,产生的只读事件会调度上面注册了acceptTcpHandler函数。
// src/networking.c 390 acceptTcpHandler(eventLoop, fd, NULL, AE_READABLE)
accept返回产生了一个新链接的fd(这里省略了不少步骤),须要重新的链接读取数据,因而为这个fd注册可读事件。
// src/networking.c 20 aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c)
因而调用了aeCreateFileEvent 为只读事件注册, 这里的步骤和上面的listen fd 同样。用epoll_ctl将fd加入到epoll fd里等待下次epoll_wait,再注册触发条件和readQueyFromClient函数,接着aeMain()继续执行等待用户的数据。
假如用户在这个链接键入redis命令例如:set foo bar,redis server端将会从aeApiPoll的epoll_wait返回,和accept同样的处理方式。返回的fd填入到fired[]数组,经过fired[fd]->fd找到是哪一个文件事件,找到events[fd]->rfielProc这个函数,就是上面注册的readQueyFromClient 函数。
// src/networking.c 816 readQueyFromClient(server.el, fd, NULL, AE_READABLE)
这个函数首先会执行read从tcp buffer读取用户键入的命令(非阻塞io),而后处理buffer,找到对应的command(lookupCommand),接下来执行这个command(call(c,cmd)),命令执行完毕须要返回结果的时候,会再次注册一个文件事件
// src/networking.c 71 aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c)
这样下次循环epoll_wait的时候就发现这个fd可写,因而就会执行sendReplyToClient,讲结果发送给client。
redis的这个网络事件库是比较标准的网络框架的模式,实现的功能不算多但够用。
[...] redis的网络事件库,咱们在前面的文章已经讲过,readQueryFromClient先从fd中读取数据,先存储在c->querybuf里(networking.c 823)。接下来函数processInputBuffer来解析querybuf,上面说过若是是telnet发送的裸协议数据是没有*打头的表示参数个数的辅助信息,针对telnet的数据跳到processInlineBuffer函数,而其余则经过函数processMultibulkBuffer。 这两个函数的做用同样,解析c->querybuf的字符串,分解成多参数到c->argc和c->argv里面,argc表示参数的个数,argv是个redis_object的指针数组,每一个指针指向一个redis_object, object的ptr里存储具体的内容,对于”get a“的请求转化后,argc就是2,argv就是 [...]