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);
...

        根據方法名基本就可大概猜出方法作用,此處就不多做介紹啦!

 

 

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