210125 개발일지(49일차) - 컴퓨터 시스템 11장 웹서버 프로젝트(4) : getaddrinfo()함수 및 addrinfo 구조체 뜯어보기

getaddrinfo()함수 뜯어보기

getaddrinfo()함수는 domain address를 받아서 네트워크 주소 정보(IP address)를 가져오는 함수이다.
예를 들어, "http://www.google.co.kr" 라는 domain address가 있는데, 이 주소는 사람이 알아보기 쉬운 주고이긴 하지만, 컴퓨터는 이 주소를 가지고 해당되는 구글의 서버를 찾아가지 못한다. 그래서 이 domain address와 대응되는 IP주소가 무엇인지를 알아 낸 뒤에 그 IP주소로 연결을 해야한다.
즉, Domain address -> IP address 변환을 하고 싶을 때 사용하는 함수라는 뜻이다. 이걸 전문용어로 DNS (Domain Name System/Service) resolving 이라고 한다.
그 중, 1~3번째는 입력 매개변수이고, 4번째 매개변수는 결과를 사용자에게 돌려주는 출력 매개변수이다.
결과는 addrinfo 구조체(strcut addrinfo) 의 linked list로 돌려준다.
이 결과는 사용을 끝낸 뒤엔 freeaddrinfo 함수로 메모리 해제를 해주어야 한다. 그렇지 않으면 메모리 누수가 발생한다.

  • getaddrinfo()함수는 아래와 같이 4개의 매개변수를 갖는다.
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
    
int getaddrinfo(const char *host,     // e.g. "www.example.com" or IP
                const char *service,  // e.g. "http" or port number
                const struct addrinfo *hints,
                struct addrinfo **result);
  1. host : 도메인 이름이거나 ip주소(ex:12.23.xx.xx)다.
  2. service : "http", "ftp"와 같은 서비스 이름 또는 "80"과 같은 포트번호가 올 수 있다.
    (위의 host와 service에는 NULL을 넣을 수 있다. 그러나 최소 둘 중 하나는 명시돼있어야 한다.)
  3. hints : 선택적 으로 사용할 수 있는 인자다. getaddrinfo() 함수가 리턴하는 result에 해당하는 주소구조체 안에 여러 정보를 너흥ㄹ 수 있다.
  4. result : DNS서버로부터 받은 네트워크 주소 정보를 돌려주는 output 매개변수이다. addrinfo 구조체를 사용하며, 링크드리스트다. 이 addrinfo 구조체에 대해서는 아래 그림을 참고하면 이해하기 쉽다. result의 내용 중 필요한 것들은 적절히 copy하여 사용자의 변수로 옮겨두어야 하며, result는 사용이 끝나는 즉시 freeaddrinfo() 함수로 메모리 해제를 해주어야한다.
  5. 리턴값 : 성공 시 0, 실패 시 에러코드를 리턴한다.

차근차근 계속 알아가보자.

함수 에러 시 리턴하는 에러값들은 아래와 같이 다양하다. 상세 정보를 정리해준 블로그 주소(https://techlog.gurucat.net/293)다.
EAI_ADDRFAMILY, EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NODATA, EAI_NONAME, EAI_SERVICE, EAI_SOCKET, EAI_SYSTEM 등..

gai_strerror()함수 : 리턴된 에러값을 해석해주는 함수

아래와 같이 사용하며, 사람이 읽을 수 있는 문자열로 에러값을 해석(변환)해준다.

const char *gai_strerror(int errcode);

addrinfo 구조체 뜯어보기

getaddrinfo()함수의 인자로 들어가는 구조체 중 addrinfo가 있는데, 아래와 같이 정의된다.
"netdb.h" 헤더파일에 정의돼있다.

struct addrinfo {
    int			ai_flags;     	/* 추가적인 옵션을 정의 할 때 사용, 여러 flag를 bitwise OR-ing 하여 넣는다.*/
    int			ai_family;    	/* address family를 AF_INET등과 같이 나타낸다. */
    int			ai_socktype;  	/* 소켓 타입으로 SOCK_STREAM등과 같이 나타낸다. */
    int			ai_protocol;  	/* 0혹은 IPv4와 IPv6에 대한 IPPROTO_XXX와 같은 값을 가진다.  */
    char  	       *ai_canonname;   /* host의 canonical name을 나타낸다. */
    size_t      	ai_addrlen;     /* 소켓 주소인 ai_addr 길이를 나타낸다. */
    struct sockaddr    *ai_addr;   	/* ai_addr은 소켓 주소 구조체를 가리키는 포인터다. */
    struct addrinfo    *ai_next;	/* addrinfo는 링크드 리스트로, 다음 데이터 포인터를 가리킨다. */
};

getaddrinfo()에서 hints 인자를 넣을 때, 위의 int형 4개 필드에만(최대 4개) 값을 지정해준다. 나머지는 0(또는 NULL)이어야 한다. 실제 우리는 memset을 활용해서 전체 구조체를 0으로 설정하고, 4개 이하의 필드에만 값을 지정해준다.

  • ai_flags : 이 필드는 기본 동작을 수정해준다. 여러 예시들을 알아보자.
    - AI_PASSIVE : 이 경우, host 인자는 NULL이어야 한다. 클라이언트가 connect()함수를 호출할 때 활성화된 소켓으로 이용할 수 있는 소켓 주소를 가져온다.
    - AI_CANONNAME : 기본적으로 ai_canonname필드는 NULL이다. 이 플래그가 설정되면 addrinfor 구조체 첫 번째의 ai_canonname필드에 가서 host의 규범적(canonical) 이름을 나타낸다.
    - AI_NUMERICHOST : 호스트 이름으로 "12.23.12.23"과 같이 숫자로 된 IP주소를 사용함을 의미한다.
    - AI_NUMERICSERV : 기본적으로 service 인자는 서비스이름이나 포트번호인데, 이 플래그가 사용되면 service 인자가 포트번호여야 한다.
  • ai_family : 기본적으로 getaddrinfo()는 IPv4와 IPv6 소켓 주소를 리턴한다.
    - AF_INET : IPv4 주소로 리턴한다.
    - AF_INET6 : IPv6 주소로 리턴한다.
    - AF_UNSPEC : 프로토콜에 관계없이, 사용할 수 있다.
  • ai_socktype : 소켓의 종류 3가지에 따라 적어준다.
    - SOCK_STREAM : TCP
    - SOCK_DGRAM : UDP
    - SOCK_RAW : RAW
  • ai_protocol : 0 또는 IPPROTO_

ai_addrlen의 type관련 주의사항

어떤 운영체제에서는 size_t가 아닌 socklen_t를 ai_addrlen으로 사용하기도 한다.
대부분의 소켓함수에서 두 type은 호환되지만, 호환불가능한 경우 런타임 오류룰 발생시키는 경우가 있다.
예를 들면, big-endian을 사용하는 64-bit Solaris 9 시스템은 size_t는 8바이트, socketlen_t는 4바이트 이므로 두 type간 호환되지 않고 런타임 오류 발생할 것이다.
따라서 본인이 작성한 코드가 어떤 시스템 위에서 동작할 지를 충분히 고려해야 한다.
(출처: https://techlog.gurucat.net/294)

getaddrinfo() 함수를 활용한 예제

www.google.com의 80포트 네트워크 주소를 얻는 예제다. (출처 블로그는 위와 같다.)

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int main(int argc,char *argv[])
{   int status;
    struct addrinfo hints;
    struct addrinfo *servinfo; // 결과를 저장할 변수
    
    memset(&hints, 0, sizeof(hints)); // hints 구조체의 모든 값을 0으로 초기화
    hints.ai_family = AF_UNSPEC; // IPv4와 IPv6 상관하지 않고 결과를 모두 받겠다
    hints.ai_socktype = SOCK_STREAM; // TCP stream socket
    status = getaddrinfo("www.google.com", "80", &hints, &servinfo); 
}

좋은 웹페이지 즐겨찾기