Domain Name System
IP주소와 도메인 이름 사이에서의 변환을 수행하는 시스템을 가리켜 DNS(Domain Name System)
이라고 한다 DNS의 중심에는 DNS서버가 있다.
도메인 이름
인터넷에서 서비스를 제공하는 서버들 역시 IP주소로 구분이 된다.
그러나 IP주소는 기억하기도 쉽지 않다. 때문에 기억하기도 좋고 표현하기에도 좋은 형태의 도메인 이름이라는 것을 IP주소에 부여해서, 이것을 IP주소 대신에 사용하고 있다.
즉, IP를 대신하는 서버의 주소이며 실제 접속에 사용되는 주소는 아니다.
이 정보는 IP로 변환이 되어야 접속이 가능하다.
DNS 서버
인터넷 브라우저 주소 창에 IP주소 xxx.xx.xxx.x를 직접 입력하면 해당하는 홈페이지의 메인 페이지를 볼 수 있다. 그러나 일반적으로 IP주소로 들어가지 않고 http://www.naver.com과 같은 입력을 통해서 접속한다.
이 두 접속 방법에는 어떠한 차이점이 있을까 ?
접속의 과정에 차이가 있다.
도메인 이름은 해당 서버에 부여된 가상의 주소이지 실제 주소가 아니다.
때문에 가상의 주소를 실제 주소로 변환하는 과정을 거쳐서 해당 홈페이지에 접속해야 한다.
도메인 이름을 IP주소로 변환을 담당하는 것이 DNS 서버이다. 다음과 같은 과정을 거친다.
"DNS 서버야, http://www.naver.com의 IP주소가 어떻게 되니 ?"
모든 컴퓨터에는 Default DNS 서버의 주소가 등록되어 있다.
바로 이 DNS 서버를 통해서 도메인 이름에 대한 IP 주소 정보를 얻게 된다.
즉 우리들이 인터넷 브라우저 주소 창에 도메인 이름을 입력하면 인터넷 브라우저는 해당 도메인 이름의 IP주소를 디폴트 DNS 서버를 통해 얻게 되고 그다음에야 비로소 서버로의 실제 접속에 들어가는것
참고로 컴퓨터에 설정된 디폴트 DNS 서버가 모든 도메인의 IP주소를 알지는 못한다.
그러나 디폴트 DNS 서버는 모르면 다른 DNS서버에 물어서라도 가르쳐 준다.
위 그림은 호스트가 문의한 도메인 이름의 IP주소를 디폴트 DNS 서버가 모르는 상황에 대한 응답과정을 보이고 있다.
이 그림에서 보이듯 디폴트 DNS 서버는 자신이 모르는 정보에 대한 요청이 들어오면 한단계 상위 계층에 있는
DNS 서버에게 믈어본다. 이러한 식으로 계속 올라가다 보면 최상위 DNS 서버인 Root DNS 서버에게까지 질의가 전달되는데
Root DNS 서버는 해당 질무능ㄹ 누구에게 재 전달해야 하는지 알고있다.
그래서 자신보다 하위에 있는 DNS 서버에게 다시 질의를 던져서 결국은 IP주소를 얻어내며 그 결과는 질의가 진행된 반대 방향으로 전달이 되어 결국에는 질의를 시작한 호스트에게 IP 주소가 전달된다
이렇듯 DNS는 계층적으로 관리되는 일종의 분산데이터베이스 시스템이다.
IP주소와 도메인 이름 사이의 변환
프로그램 상에서 도메인 이름을 쓸 필요가 있는가 ?
이 블로그를 읽는 독자들이 www.abcde.com이라는 도메인을 운영하는 회사의 시스템 엔지니어라고 가정해보자.
그리고 여러분은 회사의 서비스 사용을 위한 클라이언트 프로그램을 개발해야 한다.
사용자에게 서비스를 제공하기 위해서 계속해서 IP와 PORT번호를 입력하게 한다면 불편함을 느낄 것이다.
그렇다고 미리 프로그램 코드 상에 IP와 PORT번호를 넣는 것도 불편함을 불러일으킨다.
IP는 도메인 이름에 비해 상대적으로 변동이 심하다.
때문에 프로그램 코드상에서 서버의 IP를 직접 코드로 입력한다면, 서버의 IP가 변경될 때마다 컴파일을 다시 해야 하는 상황이 발생한다. 그러나 상대적으로 변동이 덜한 도메인 이름을 이용해서 서버가 실행될 때마다 IP를 얻어오게 구혀난다면, 서버의 코드를 다시 컴파일 할 필요가 없다.
도메인 이름을 이용해서 IP 주소 얻어오기
#include <netdb.h>
struct hostent * gethostbyname(const char * hostname);
// 성공 시 hostent 구조체 변수의 주소 값, 실패 시 NULL 포인터 반환
// hostname은 도메인의 이름 (www.abcde.com)
/*
struct * h_name; // official name
struct ** h_aliases; // alias list
int h_addrtype; // host address type
int h_length; // address length
char ** h_addr_list; // char ** h_addr_list;
*/
gethostbyname 함수의 인자로 도메인의 이름(www.abcde.com)정보를 전달하면, 해당 도메인의 서버 정보가 hostent 구조체 변수에 채워지고, 그 변수의 주소 값이 반환된다.
h-name
공식 도메인 이름(Official domain name)이라는 것이 저장된다.
해당 홈페이지를 대표하는 도메인 이름이라는 의미를 담고 있지만, 실제 우리에게 잘 알려진 유명한 회사의 도메인 이름이 공식 도메인 이름으로 등록되지 않은 경우도 많으니 당황하지말자.
h_aliases
같은 메인 페이지임에도 다른 도메인 이름으로 접속할 수 있는 경우를 본 적이 있다면 이해하기 쉬울 것이다.
하나의 IP에 둘 이상의 도메인 이름을 지정하는 것이 가능하기 때문에, 공식 도메인 이름이외에 해당 메인 페이지에 접속할 수 있는 다른 도메인 이름의 지정이 가능하다.
그리고 이는 h_aliasese를 통해서 얻을 수 있다.
h_addrtype
gethostbyname 함수는 IPv4 뿐만 아니라 IPv6까지 지원한다. 때문에 h_addr_list로 반환된 IP주소의 주소 체계에 대한 정보를 이 멤버를 통해 반환된다.
IPv4의 경우 AF_INET가 저장된다.
h_length
함수호출의 결과로 반환된 IP주소의 크기정보가 담긴다.
IPv4의 경우 4byte이므로 4가 저장되고, IPv6는 16byte이므로 16이 저장된다.
h_addr_list
가장 중요한 멤버이다.
이 멤버를 통해서 도메인 이름에 대한 IP주소가 정수의 형태로 반환된다.
참고로 접속자수가 많은 서버는 하나의 도메인 이름에 대응하는 IP를 여러 개 둬서, 둘 이상의 서버로 부하를 분산 시킬 수 있다.
이러한 경우에도 이 멤버를 통해서 모든 IP의 주소 정보를 얻을 수 있다.
이제 예제를 하나 소개할 건데 이 예제는 gethostbyname 함수의 활용을 보이기 위해 그리고 앞서 설명하지 못한
hostent 구조체 변수의 특성을 설명하기 위해 보여주는 예제다
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
int i;
struct hostent *host;
if(argc!=2) {
printf("Usage : %s <addr>\n", argv[0]);
exit(1);
}
host=gethostbyname(argv[1]);
if(!host)
error_handling("gethost... error");
printf("Official name: %s \n", host->h_name);
for(i=0; host->h_aliases[i]; i++)
printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
printf("Address type: %s \n",
(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");
for(i=0; host->h_addr_list[i]; i++)
printf("IP addr %d: %s \n", i+1,
inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
host=gethostbyname(argv[1]);
main 함수를 통해서 전달된 문자열을 인자로 gethostbyname 함수를 호출하고 있다.
printf("Official name: %s \n", host->h_name);
공식 도메인 이름을 출력하고 있다.
for(i=0; host->h_aliases[i]; i++)
printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
공식 도메인 이름 이외의 도메인 이름을 출력하고 있다. 반복문을 이렇게 구성한 이유는 위에 hostent 구조체 변수 그림에서 이해 할 수 있다.
for(i=0; host->h_addr_list[i]; i++)
printf("IP addr %d: %s \n", i+1,
inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
IP 주소정보를 출력하고 있다. 그런데 이해할 수 없는 형변환을 진행하고 있다.
구조체 멤버 h_addr_list가 가리키는 것은 문자열 포인터 배열(둘 이상의 문자열 주소 값으로 구성된 배열)이다. 그러나 문자열 포인터 배열이 실제 가리키는 것은(실제 저장하고 있는 것은) 문자열의 주소 값이 아닌 in_addr 구조체 변수의 주소 값이다.
위 그림은 구조체 멤버 h_addr_list의 참조 관계를 보인다.
때문에 형변환 및 inet_ntoa 함수의 호출을 동반하는 것이다.
추가로 정보의 끝은 NULL로 표시가 된다는 사실도 기억하자.
◈ 왜 in_addr*이 아닌 char*인가요?
in_addr 이 아닌 char인 이유를 설명하겠다.
h_addr_list가 가리키는 배열이 구조체 in_addr의 포인터 배열이 아닌 char형 포인터 배열인 이유는 구조체 hostent는 IPv4만을 위해 정의된 구조체가 아니다. h_addr_list가 가리키는 배열에는 IPv6 기반의 주소 정보가 저장될 수도 있다.
때문에 일반화를 위해서 char형 포인터 배열로 선언한 것이다.
소켓관련 함수들은 void형 포인터가 표준화되기 이전에 정의되었기 때문에 아직까지 char형 포인터 변수를 활용한다.
IP주소를 이용해서 도메인 정보 얻어오기
앞서 소개한 gethostbyname 함수는 도메인 이름을 이용해서 IP주소를 포함한 도메인 정보를 얻을 때 호출하는 함수다 반면
이번에 소개하는 gethostbyaddr 함수는 IP주소를 이용해 도메인 정보를 얻을 때 호출 하는 함수다.
#include <netdb.h>
struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family);
// 성공 시 hostent 구조체 변수의 주소 값, 실패시 NULL 포인터 반환
/*
addr : IP주소를 지니는 in_addr 구조체 변수의 포인터 전달, IPv4 이외의 다양한 정보를
전달받을 수 있도록 일반화하기 위해서 매개변수를 char형 포인터로 선언
len : 첫 번째 인자로 전달된 주소 정보의 길이, IPv4의 경우 4, IPv6의 경우 16 전달
family : 주소체계 정보 전달. IPv4의 경우 AF_INET, IPv6의 경우 AF_INET6 전달
*/
gethostbyname 함수에 대한 이해가 있다면 위 함수는 쉽게 이해가 가능하므로 예시를 통해서 설명하겠다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
int i;
struct hostent *host;
struct sockaddr_in addr;
if(argc!=2) {
printf("Usage : %s <IP>\n", argv[0]);
exit(1);
}
memset(&addr, 0, sizeof(addr));
addr.sin_addr.s_addr=inet_addr(argv[1]);
host=gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
if(!host)
error_handling("gethost... error");
printf("Official name: %s \n", host->h_name);
for(i=0; host->h_aliases[i]; i++)
printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
printf("Address type: %s \n",
(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");
for(i=0; host->h_addr_list[i]; i++)
printf("IP addr %d: %s \n", i+1,
inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
host=gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
위으 함수호출 과정만 제회하면 전에 소개했던 gethostname과 크게 다른게 없다 함수호출의 결과가 hostent 구조체 변수의 주소 값을 통해 전달되기 때문
둘의 차이점은
차이점은 매개 변수의 인자가 끝이다. 반대로 생각하면 굉장히 쉽다.
'스터디 > TCP 와 IP' 카테고리의 다른 글
9. 다양한 입출력 함수들 (0) | 2024.09.03 |
---|---|
8. 멀티프로세스 기반의 서버구현 (0) | 2024.09.03 |
6. 소켓의 우아한 연결 종료 (0) | 2024.09.03 |
5. TCP 기반 서버/ 클라이언트 2 (0) | 2024.09.03 |
4. TCP 기반 서버/ 클라이언트 1 (0) | 2024.09.03 |