server.el=aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
aeCreateEventLoop()内部会调用 aeApiCreate(),选择性的调用epoll、select 或者 kqueue 的API,创建事件监听。这里拿 kqueue 来举例子,我们可以看下 ae_kqueue.c 中的实现。
staticintaeApiCreate(aeEventLoop*eventLoop){aeApiState*state=zmalloc(sizeof(aeApiState));......state->events=zmalloc(sizeof(structkevent)*eventLoop->setsize);state->kqfd=kqueue();......eventLoop->apidata=state;return0;}
上面创建了一个 aeApiState,并且调用了 kqueue 的API:kqueue():
// 创建一个内核消息队列,返回队列描述符 intkqueue(void);
这里创建了一个内核事件队列,并将该队列的文件描述符存入了 state->kqfd 中。
(2)创建套接字,监听端口
要想监听客户端的命令,首先要做的就是监听端口,initServer()在创建好 aeEventLoop 后,就会开始执行端口监听操作:
listenToPort(server.port,server.ipfd,&server.ipfd_count)==C_ERR)
其中 server.ipfd 是个数组,用来存放端口监听后创建的描述符。为什么是个数组?因为Redis 可以同时 bind 多个 IP 地址,所以listenToPort()内部会遍历配置文件中配置的多个IP地址,依次进行监听,并将创建的描述符存入 server.ipfd [] 中。
(3)注册监听到内核队列
监听好端口后,initServer()会遍历刚刚创建好的套接字描述符 ipfd [],依次注册到事件监听中。
aeCreateFileEvent(server.el,server.ipfd[j],AE_READABLE,acceptTcpHandler,NULL)
我们进入aeCreateFileEvent()中看看。
intaeCreateFileEvent(aeEventLoop*eventLoop,intfd,intmask,aeFileProc*proc,void*clientData){......aeFileEvent*fe=&eventLoop->events[fd];if(aeApiAddEvent(eventLoop,fd,mask)==-1)returnAE_ERR;......returnAE_OK;}
上面主要是初始化了 aeFileEvent,存放在 eventLoop 中的events [] 内。并且调用 aeApiAddEvent(),将监听端口的描述符注册到(1)中创建的内核队列中。对于 kqueue来说,这里调用的是 kqueue 的EV_SET():
staticintaeApiAddEvent(aeEventLoop*eventLoop,intfd,intmask){aeApiState*state=eventLoop->apidata;structkeventke;if(mask&AE_READABLE){EV_SET(&ke,fd,EVFILT_READ,EV_ADD,0,0,NULL);if(kevent(state->kqfd,&ke,1,NULL,0,NULL)==-1)return-1;}if(mask&AE_WRITABLE){EV_SET(&ke,fd,EVFILT_WRITE,EV_ADD,0,0,NULL);if(kevent(state->kqfd,&ke,1,NULL,0,NULL)==-1)return-1;}return0;}
3.2 启动事件监听循环,监听请求
经过了上面的一系列初始化操作,Redis 就会正式进入事件监听的循环中,即aeMain()。
voidaeMain(aeEventLoop*eventLoop){eventLoop->stop=0;while(!eventLoop->stop){if(eventLoop->beforesleep!=NULL)eventLoop->beforesleep(eventLoop);aeProcessEvents(eventLoop,AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);}}
这段代码看起来很简单,只要事件监听没有停止,Redis 就会一直循环调用aeProcessEvents()处理事件。
aeProcessEvents()中的核心逻辑就是两步:
(1)调用 aeApiPoll ()来等待接收事件。
numevents=aeApiPoll(eventLoop,tvp);
我们看下使用kqueue时,ae_kqueue.c 中的实现。
staticintaeApiPoll(aeEventLoop*eventLoop,structtimeval*tvp){aeApiState*state=eventLoop->apidata;intretval,numevents=0;......retval=kevent(state->kqfd,NULL,0,state->events,eventLoop->setsize,NULL);......numevents=retval;......returnnumevents;}
这里调用的是 kevent()来接收事件。kevent()描述如下:
// 用途:注册\反注册 监听事件,等待事件通知 // kq,上面创建的消息队列描述符 // changelist,需要注册的事件 // changelist,changelist数组大小 // eventlist,内核会把返回的事件放在该数组中 // nevents,eventlist数组大小 // timeout,等待内核返回事件的超时事件,NULL 即为无限等待 int kevent(int kq,	const struct kevent *changelist, int nchanges,	struct kevent *eventlist, int nevents,	const struct timespec *timeout);
(2)遍历返回的就绪事件数组,处理事件。
for(j=0;j