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進行監聽,如果綁定成功或者errno爲EAFNOSUPPORT時,就再嘗試啓動ipv4進行監聽。因爲*count和unsupported初始值都爲0,若綁定成功那麼*count值爲1;如果errno==EAFNOSUPPORT則unsupported值爲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()方法也會接收失敗的。
一點淺見,如有問題,歡迎指正!