본문 바로가기

Data Base/Redis

Redis - Server와 Client TCP/IP 통신

Redis는 Sever와 Client에서 TCP/IP를 통해 통신을 주고 받는다.  

 

Server와 Client 의 TCP/IP 통신

 

  • Redis의 코드(server.c)
int main(int argc, char **argv) { 
    ...
    initServer();
    ...
 }

 

socket 통신을 하는 부분은  initServer하는 부분에서 만들어지게 되는데 socket, bind, listen, accept가 만들어지게 된다. 

 

 initSever()를 살펴보자면

 

void initServer(void) {
    int j;
    ...
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
    if (server.tls_port != 0 &&
        listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
        exit(1);
    ...
    for (j = 0; j < server.ipfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            {
                serverPanic("...");
            }
    }
    for (j = 0; j < server.tlsfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
            acceptTLSHandler,NULL) == AE_ERR)
            {
                serverPanic("...");
            }
    }
    if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
        acceptUnixHandler,NULL) == AE_ERR) serverPanic("...");
    ...
}

 

크게 크게 보자면 listenToPort()부분에서 포트통신의 listen에 해당하는 영역이라 data를 보낼 그릇인 socket, 그리고 소켓에 포트 번호를 부여하는 bind(), listen()이 불려 클라이언트 접속을 기다리게 된다. 그 외의 부분 acceptTcpHandler()와 accpetUnixHandler()를 통해서d연결 요구를 들어주는 accept()가 불려지게 된다.

 

여기서 listenToPort는 인자를 port, *fds(file descriptors), fds의 *counter를 들고 있게된다. 조금 더 listenToPort를 살펴보자

 

int listenToPort(int port, int *fds, int *count) {
    int j;
    
    ...
    
        if (server.bindaddr[j] == NULL) {
            int unsupported = 0;
            fds[*count] = anetTcp6Server(server.neterr,port,NULL,
                server.tcp_backlog);
            ...
            
            if (*count == 1 || unsupported) {
                fds[*count] = anetTcpServer(server.neterr,port,NULL,
                    server.tcp_backlog);
            ...
            
            if (*count + unsupported == 2) break;
        } else if (strchr(server.bindaddr[j],':')) {
            fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        } else {
            fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
                server.tcp_backlog);
        }
        ...
}

 

listenToPort에서는 IPv6 또는 IPv4 모두 활용할 수 있게 해주는 부분이자 각각의 fds에서 socket과 bind, listen을 불려줌으로 각각의 fds마다 소켓과 소켓 번호 그리고 클라이언트의 보냄을 들을수 있게 해줘서 통신의 시작을 알리게 된다.

anetTcp6Server 와 anetTcpServer는 모두 _anetTcpServer를 부르게되어 있어 IPv6와 IPv4 둘다 하나의 함수로 처리한다.

이제 _anetTcpServer(anet.c)에서는 본격적으로 socket이 만들어진다.

 

static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
{
    ...
    for (p = servinfo; p != NULL; p = p->ai_next) {
        if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) 
            continue;
        ...
        
        if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
        goto end;
    }
    ...
}

 

드디어 _anetTcpServer() 불리면서 socket이 만들어지고 이에 따라서 anetListen이 불려져 각각의 socket에 주소와 들을 수 있는 귀를 만들어주게 된다. 

 

static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
    if (bind(s,sa,len) == -1) {
        anetSetError(err, "bind: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }

    if (listen(s, backlog) == -1) {
        anetSetError(err, "listen: %s", strerror(errno));
        close(s);
        return ANET_ERR;
    }
    return ANET_OK;
}

 

bind()와 listen()이 불려지면 이제 initServer()로 돌아와 accept가 만들어지는 것을 기다려야한다. 

아까 보았던 accpetTcpHandler() 와 acceptUnixHandler()가 불리면 anetTcpAccept()와 anetUnixAccept()가 불려진다. 그렇게 될 경우 anetGenericAccept()가 불려 아래와 같은 accept가 불려지게되고  

 

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

 

accept가 만들어지고 이렇게 통신을 하게된다. 

 

이걸 도표로 만든다면 아래와 같은 그림이 완성된다. 

출처 : http://redisgate.kr/redis/server/bind.php

 

이러한 흐름을 봤을 때 

Redis에서는 Server과 Client의 통신은 아래 그림과 같이 설명될 수 있다.

출처 : http://redisgate.kr/redis/server/bind.php

다음에는 Master와 Replica에서의 TCP/IP통신을 보고자 한다.