epoll多路分离机制虽然以数组的形式返回所有激活事件的fd及属性(读写),但没有指定对每个事件的操作。事实上,如果需要读写不同激活的fd,只能根据fd、属性来再次判断自己操作的是哪一个事件。在读操作的执行函数一致、写操作的执行函数一致时,这种做法是可行的。但如果读或写操作属性的事件具有不同的执行函数,就不能单纯根据事件的属性决定执行函数了。为了解决这个问题,redis提供了读写回调函数与注册事件绑定的方法,可以根据需求给相应的事件绑定相应的回调函数。
为了将fd与回调函数绑定,redis定义了aeFileEvent,如下:
/* File event structure */
typedef struct aeFileEvent {
int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
aeFileProc *rfileProc;
aeFileProc *wfileProc;
void *clientData;
} aeFileEvent;
在aeFileEvent中,存储了事件的属性、读写回调函数、待操作的客户端数据。并且在初始化时,通过aeFileEvent定义了events数组。
我们知道epoll_wait可以得到的是激活事件的fd、mask数组,那如何将激活的fd与注册事件时的回调函数联系起来呢?
先看一下添加文件事件时的函数,如下:
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
aeFileEvent *fe = &eventLoop->events[fd];
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
return AE_ERR;
fe->mask |= mask;
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
fe->clientData = clientData;
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
在aeCreateFileEvent函数中,根据添加事件的fd,将读写回调函数写到events[fd]中。也就是说,如果知道了fd,就可以在events中找到对应的读写回调函数了。这样就解决了fd与回调函数的联系问题。
最后,再看看redis中怎样使用注册fd与回调函数绑定的方式执行回调函数,如下:
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
...
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 fired = 0; /* Number of events fired for current fd. */
int invert = fe->mask & AE_BARRIER;
if (!invert && fe->mask & mask & AE_READABLE) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
/* Fire the writable event. */
if (fe->mask & mask & AE_WRITABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if (invert && fe->mask & mask & AE_READABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
return processed; /* return the number of processed file/time events */
}
在aeProcessEvents中,aeApiEpoll会将激活事件的fd、mask存储到fired数组中;
然后遍历fired中所有的激活事件,并根据索引fd定位到events的该fd的注册位置;
之后根据fired中的mask,决定使用events[fd]中的哪个回调函数,如果该fd同时具有读写属性,会先进行读操作,在进行写操作;如果设置了invert,则执行顺序相反。
1,事件在注册时,在events数组的fd索引处定义了读写回调函数;
2,epoll_wait可以返回激活事件的fd、mask;
3,在events[fd]处,根据mask的属性选择执行回调函数。