[LINUX] TINY webserver 만들기 (3) - 소켓 인터페이스 도움 함수

piopiop·2021년 1월 26일
1

Linux

목록 보기
4/6

소켓 인터페이스와 getaddrinfo 함수를 처음 배웠을 때 바로 사용하기란 쉽지 않을 것이다.
그래서 이번에는 한번에 클라이언트의 clientfd, 서버의 listenfd를 생성해주는 함수를 만들어 사용해보자.
두 함수는 이전 두 포스팅에서 다뤘던 소켓 인터페이스의 함수들과 getaddrinfo 함수를 이용한다.

(1) open_clientfd

open_clientfd 함수의 내용은 다음과 같다.
1. 접속을 원하는 서버와 포트의 주소를 getaddrinfo 함수를 통해 가져온다.
2. getaddrinfo를 통해 생성된 addrinfo 연결리스트 내 주소들과 connect를 시도한다.
3. connect에 성공한 clientfd를 반환한다.

int open_clientfd(char *hostname, char *port) {
    int clientfd, rc;
    struct addrinfo hints, *listp, *p;

    //hints의 모든 필드 비우고 hints만들기
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;  //TCP소켓만 반환
    hints.ai_flags = AI_NUMERICSERV;  //포트 넘버로 반환
    hints.ai_flags |= AI_ADDRCONFIG;  //로컬 호스트가 와 같은 프로토콜을 사용하는 주소 반환
    
    //getaddrinfo 함수 실행 (실패 시 -2 반환)
    if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
        return -2;
    }
  
    //getaddrinfo를 통해 얻은 addrinfo 연결리스트를 돌며 connet 시도 
    for (p = listp; p; p = p->ai_next) {
    	//clientfd 생성
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue; //소켓 생성 실패하면 연결리스트의 다음 원소로

	//연결에 성공했으면 for문을 나가고 연결에 성공한 clientfd 반환
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) 
            break; 
        //연결에 실패했으면 clientfd 소켓을 닫고 연결리스트의 다음 원소로
        if (close(clientfd) < 0) {  
            fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
            return -1;
        } 
    } 
	
    //연결리스트 메모리 해제
    freeaddrinfo(listp);
    if (!p) //모든 connect가 실패했을 때
        return -1;
    else    
        return clientfd;
}

(2)open_listenfd

open_listenfd 함수도 open_clientfd 함수와 내용이 비슷하다.
1. getaddrinfo를 실행해 addrinfo 구조체의 연결리스트를 생성한다.
이때 host를 NULL로 설정하면 호스트 주소는 localhost가 할당이 된다.
2. getaddrinfo를 통해 생성된 addrinfo 연결리스트 내 주소들과 bind를 시도한다.
3. bind에 성공하면 bind에 성공한 소켓을 listen함수에 인자로 넣어 듣기 소켓으로 만들고 이를 반환한다.
4. bind에 실패했다면 연결리스트의 남은 주소들과 bind를 시도한다.

int open_listenfd(char *port) 
{
    struct addrinfo hints, *listp, *p;
    int listenfd, rc, optval=1;
	
    //hints의 모든 필드 비우고 hints만들기
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;             
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; //AI_PASSIVE를 설정하면 듣기 소켓으로 이용할 수 있는 소켓 주소를 리턴함. 이때 host인자는 NULL이어야함. 
    hints.ai_flags |= AI_NUMERICSERV;            
    if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
        return -2;
    }

    //getaddrinfo를 통해 얻은 addrinfo 연결리스트를 돌며 bind 시도 
    for (p = listp; p; p = p->ai_next) {
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue;  

        
        //기존의 bind 되었던 소켓을 재사용하기 위해 SO_REUSEADDR 인자를 넣어 소켓의 옵셔을 변경해준다. (Eliminates "Address already in use" error from bind)
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));

        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            break; 
        if (close(listenfd) < 0) { 
            fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
            return -1;
        }
    }


    freeaddrinfo(listp);
    if (!p) 
        return -1;
	
    //bind에 성공한 소켓이 있나면 listen함수에 인자로 넣어줘 연결 요청을 받을 수 있는 듣기 소켓으로 변환
    if (listen(listenfd, LISTENQ) < 0) {
        close(listenfd);
	return -1;
    }
    return listenfd;
}
profile
piopiop1178@gmail.com

0개의 댓글