redis之啓動服務監聽

        redis是用c語言實現的一個內存數據庫,先從server.c文件中的main()方法看起:

int main(int argc, char **argv) {
    ...
    initServerConfig();
    ...
    initServer();
    ...
}

        main()方法乾的事很多,這裏只研究啓動服務以及監聽這塊,主要就是上面兩個方法。initServerConfig()方法主要就是給server結構體賦初始值,部分代碼如下:

void initServerConfig(void) {
    ...
    server.timezone = getTimeZone(); /* Initialized by tzset(). */
    server.configfile = NULL;
    server.executable = NULL;
    server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
    server.bindaddr_count = 0;
    server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
    server.ipfd_count = 0;
    server.tlsfd_count = 0;
    server.sofd = -1;
    ...
}

        其實這方法也只是給一部分元素賦值,還有部分值是爲null的。可以看到server.ipfd_count初始值爲0,這個後面會提到。啓動服務的主要工作都是在initServer()方法中:

void initServer(void) {
    ...
    /* Open the TCP listening socket for the user commands. */
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
    ...

    /* Abort if there are no listening sockets at all. */
    if (server.ipfd_count == 0) {
        serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
        exit(1);
    }
    ...
    /* 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.");
            }
    }
    ...
}

        這裏我們只研究不加密的tcp服務,所以關於tls部分和unix域套接字部分都省去了,當然還有server結構體的部分字段的初始化。在initServer()方法中是調用了listenToPort()方法啓動監聽的,第三個參數就是前面提到的server.ipfd_count,並且是引用。

/*
 * On success the function returns C_OK.
 * On error the function returns C_ERR. 
 */
int listenToPort(int port, int *fds, int *count) {
    int j;

    /* Force binding of 0.0.0.0 if no bind address is specified, always
     * entering the loop if j == 0. */
    if (server.bindaddr_count == 0) server.bindaddr[0] = NULL;
    for (j = 0; j < server.bindaddr_count || j == 0; j++) {
        if (server.bindaddr[j] == NULL) {
            int unsupported = 0;
            /* Bind * for both IPv6 and IPv4, we enter here only if
             * server.bindaddr_count == 0. */
            fds[*count] = anetTcp6Server(server.neterr,port,NULL,
                server.tcp_backlog);
            if (fds[*count] != ANET_ERR) {
                anetNonBlock(NULL,fds[*count]);
                (*count)++;
            } else if (errno == EAFNOSUPPORT) {
                unsupported++;
                serverLog(LL_WARNING,"Not listening to IPv6: unsupported");
            }

            if (*count == 1 || unsupported) {
                /* Bind the IPv4 address as well. */
                fds[*count] = anetTcpServer(server.neterr,port,NULL,
                    server.tcp_backlog);
                if (fds[*count] != ANET_ERR) {
                    anetNonBlock(NULL,fds[*count]);
                    (*count)++;
                } else if (errno == EAFNOSUPPORT) {
                    unsupported++;
                    serverLog(LL_WARNING,"Not listening to IPv4: unsupported");
                }
            }
            /* Exit the loop if we were able to bind * on IPv4 and IPv6,
             * otherwise fds[*count] will be ANET_ERR and we'll print an
             * error and return to the caller with an error. */
            if (*count + unsupported == 2) break;
        } else if (strchr(server.bindaddr[j],':')) {
            /* Bind IPv6 address. */
            fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        } else {
            /* Bind IPv4 address. */
            fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        }
        if (fds[*count] == ANET_ERR) {
            serverLog(LL_WARNING,
                "Could not create server TCP listening socket %s:%d: %s",
                server.bindaddr[j] ? server.bindaddr[j] : "*",
                port, server.neterr);
                if (errno == ENOPROTOOPT     || errno == EPROTONOSUPPORT ||
                    errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
                    errno == EAFNOSUPPORT    || errno == EADDRNOTAVAIL)
                    continue;
            return C_ERR;
        }
        anetNonBlock(NULL,fds[*count]);
        (*count)++;
    }
    return C_OK;
}

        該方法先啓動ipv6進行監聽,如果綁定成功或者errnoEAFNOSUPPORT時,就再嘗試啓動ipv4進行監聽。因爲*countunsupported初始值都爲0,若綁定成功那麼*count值爲1;如果errno==EAFNOSUPPORTunsupported值爲1,這樣下面的if判斷條件就必然爲真從而執行。

        這裏解釋下EAFNOSUPPORT的含義,也就是address family not supported的意思。目前socket()函數只支持常見的五種協議族:

family 說明
AF_INET ipv4協議
AF_INET6 ipv6協議
AF_LOCAL unix域協議
AF_ROUTE 路由套接字
AF_KEY 密鑰套接字

        只要ip地址綁定成功,暫且不管協議支持與否,函數都會返回成功。

        再回到initServer()方法中,第二個if()語句是根據server.ipfd_count的值判斷是否有綁定成功,如果成功,則程序繼續往下執行,調用aeCreateFileEvent()方法,加入到event handler中,並給一個回調函數acceptTcpHandler(),當服務監聽到請求時便調用該函數進行接收。

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    ...
    while(max--) {
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        if (cfd == ANET_ERR) {
            if (errno != EWOULDBLOCK)
                serverLog(LL_WARNING,
                    "Accepting client connection: %s", server.neterr);
            return;
        }
        serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
        acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
    }
}

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

    if (sa.ss_family == AF_INET) {
        struct sockaddr_in *s = (struct sockaddr_in *)&sa;
        if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
        if (port) *port = ntohs(s->sin_port);
    } else {
        struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
        if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
        if (port) *port = ntohs(s->sin6_port);
    }
    return fd;
}

static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
    int fd;
    while(1) {
        fd = accept(s,sa,len);
        if (fd == -1) {
            if (errno == EINTR)
                continue;
            else {
                anetSetError(err, "accept: %s", strerror(errno));
                return ANET_ERR;
            }
        }
        break;
    }
    return fd;
}

        上面這三個方法其實來自不同的文件,這裏圖方便我就放在一塊了。從上往下依次調用,可見到最後還是無限循環調用我們熟悉的accept()方法接收客戶端請求。

        至此,redis服務的啓動到監聽就理得差不多了,中間省略了很多地方,以及事件處理器也沒有介紹,以後再抽時間介紹吧。

        其實關於 listenToPort() 方法中 *count+unsupported==2 這塊我想了很久之前一直不理解這塊意思。總覺得如果if判斷後,*count=0,unsupported=2,那麼就說明 fds[*count] 是等於 ANET_ERR 的,也是屬於失敗的一種,應該返回失敗纔對,而這裏也是直接跳出循環返回成功。後來想到可能是設計者考慮到以後可能會增加新的協議族,在後續流程中處理即可。譬如目前如果是其他的不支持的協議族,accept()方法也會接收失敗的。

        一點淺見,如有問題,歡迎指正!

 

 

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