redis之事件驱动

        redis服务器是一个事件驱动程序,服务器主要处理以下两类事件:

  • 文件事件:redis服务器通过套接字与客户端进行连接,通信时会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。简言之文件事件就是服务器对套接字操作的抽象;
  • 时间事件:redis服务器中的一些操作需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象。

        本文只借文件事件来研究redis中事件,时间事件以后再介绍。这里还是从initServer()方法中的部分代码看起:

...
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
...
// Create an event handler for accepting new connections in TCP and Unix domain sockets.
for (j = 0; j < server.ipfd_count; j++) {
    if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) {
        serverPanic("Unrecoverable error creating server.ipfd file event.");
    }
}
...
if (server.sofd > 0 && aeCreateFileEvent(server.el, server.sofd, AE_READABLE, acceptUnixHandler,NULL) == AE_ERR) {
    serverPanic("Unrecoverable error creating server.sofd file event.");
}
...

        先是调用了aeCreateEventLoop()方法创建以及初始化aeEventLoop结构体并返回复制给server.el。方法及结构体如下:

/* State of an event based program */
typedef struct aeEventLoop {
    int maxfd;   /* highest file descriptor currently registered */
    int setsize; /* max number of file descriptors tracked */
    long long timeEventNextId;
    time_t lastTime;     /* Used to detect system clock skew */
    aeFileEvent *events; /* Registered events */
    aeFiredEvent *fired; /* Fired events */
    aeTimeEvent *timeEventHead;
    int stop;
    void *apidata; /* This is used for polling API specific data */
    aeBeforeSleepProc *beforesleep;
    aeBeforeSleepProc *aftersleep;
    int flags;
} aeEventLoop;

aeEventLoop *aeCreateEventLoop(int setsize) {
    aeEventLoop *eventLoop;
    int i;

    if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
    eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
    if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
    eventLoop->setsize = setsize;
    eventLoop->lastTime = time(NULL);
    eventLoop->timeEventHead = NULL;
    eventLoop->timeEventNextId = 0;
    eventLoop->stop = 0;
    eventLoop->maxfd = -1;
    eventLoop->beforesleep = NULL;
    eventLoop->aftersleep = NULL;
    eventLoop->flags = 0;
    if (aeApiCreate(eventLoop) == -1) goto err;
    /* Events with mask == AE_NONE are not set. So let's initialize the
     * vector with it. */
    for (i = 0; i < setsize; i++)
        eventLoop->events[i].mask = AE_NONE;
    return eventLoop;
    ...
}

        结构体中定义了文件事件、时间事件、已注册事件的最大描述符等信息,初始化时maxfd=-1表明当前还没有注册任何事件。回到上文的initServer()方法中,下面两部分分别是将tcp套接字和unix域套接字通过aeCreateFileEvent()方法创建文件事件,并针对不同的事件类型,事件处理程序也不同。如这里tcp套接字的事件处理程序是acceptTcpHandler()方法;而unix域套接字的事件处理程序是acceptUnixHandler()方法。当服务端接收到应答不同套接字的信息,就会调用对应的事件处理程序进行处理。

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()方法中实际是调用aeApiAddEvent()方法将套接字注册到之前定义的server.el中,这里如果选择的是epoll系统调用,那么aeApiAddEvent()方法体如下:

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; /* avoid valgrind warning */
    /* If the fd was already monitored for some event, we need a MOD
     * operation. Otherwise we need an ADD operation. */
    int op = eventLoop->events[fd].mask == AE_NONE ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;

    ee.events = 0;
    mask |= eventLoop->events[fd].mask; /* Merge old events */
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.fd = fd;
    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    return 0;
}

        可以看到实际上调用的还是epoll_ctl()方法。再回到aeCreateFileEvent()方法中,同时也将事件处理程序也赋值给server.el结构体中对应套接字的文件事件结构体中。

/* File event structure */
typedef struct aeFileEvent {
    int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
    aeFileProc *rfileProc;
    aeFileProc *wfileProc;
    void *clientData;
} aeFileEvent;

        这里在写法上它并不是直接将事件处理程序赋值,而是声明了一个指向该套接字对应的文件事件结构体的指针*fe,我猜测是这样后面赋值语句写起来会简洁些。

        当有客户端连接到服务器中监听的套接字时,文件事件分发器就会调用对应的事件处理程序进行处理。假设这里是tcp连接,调用的也就是acceptTcpHandler()方法。

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    ...
    while(max--) {
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        ...
        acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
    }
}

int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
    ...
    if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
        return ANET_ERR;
    ...
    return fd;
}

static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
    int fd;
    while(1) {
        fd = accept(s,sa,len);
        ...
    }
    return fd;
}

       上述三个方法从上往下依次调用,包括创建socket、通过该socket与客户端建立连接然后进行处理等等。

        关于文件事件上文只介绍了其中少数几个方法,如aeCreateFileEvent()方法是接受一个套接字描述符、一个事件类型以及一个事件处理程序作为参数,将给定的套接字事件加入到I/O多路复用程序监听的范围之内,并将事件和事件处理程序关联起来。实际上还有其他几个api:

aeEventLoop *aeCreateEventLoop(int setsize);
void aeDeleteEventLoop(aeEventLoop *eventLoop);
void aeStop(aeEventLoop *eventLoop);
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData);
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
int aeGetFileEvents(aeEventLoop *eventLoop, int fd);
...

        根据方法名基本就可大概猜出方法作用,此处就不多做介绍啦!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章