프로그램상에서 도메인 이름을 쓸 필요가 있는가?
제가 www.justweon.com이라는 도메인을 운영하는 회사의 시스템 엔지니어라고 가정해보겠습니다. 그리고 회사의 서비스 사용을 위한 클라이언트 프로그램을 개발한다고 할 때, 클라이언트 프로그램은 IP가 211.102.204.12이고 PORT가 2022인 주소에 접속해서 서비스를 받도록 설계되어야 합니다.
프로그램 사용자에게는 편리한 실행방법을 제공해야 합니다. 프로그램 사용자가 IP와 PORT를 직접 입력해야만 서비스를 받을 수 있는 프로그램이 되어서는 안되겠죠. 그렇다면 어떻게 위에 말한 주소정보를 프로그램에 삽입할 수 있을까요? 주소 정보를 직접 삽입해서 구현을 해도 물론 정상적이게 작동은 될 것입니다. 그러나 시스템과 관련된 여러 가지 이유로 IP를 변경해야 할 경우가 생긴 경우를 생각해보면, 이를 근거로 사용자들에게 지금 사용하는 프로그램은 이러이러한 이유로 사용이 불가능하니 홈페이지에 들어가서 신버전을 다운로드하라고 한다면 사용자들은 당연히 불편을 느낄 것입니다. IP 주소는 도메인 이름에 비해 상대적으로 변경의 확률이 높습니다. 때문에 IP 주소를 바탕으로 프로그램을 작성하는 것은 좋은 방법이 아닙니다. 도메인 이름은 일단 등록하면 평생 유지가 가능하니 이를 이용해 코드를 작성하는 편이 나을 수 있습니다. 이렇게 되면 프로그램은 도메인 이름을 근거로 IP를 얻어온 다음 서버에 접속하게 되니 서버의 IP 주소로부터 클라이언트 프로그램은 자유로워질 수 있습니다. 그래서 IP주소와 도메인 이름 사이의 변환함수가 필요합니다.
도메인 이름을 이용해 IP 주소 얻어오기
다음 함수를 이용하면 문자열 형태의 도메인 이름으로부터 IP의 주소정보를 얻을 수 있습니다. 이 함수에는 다음과 같은 인자를 전달해줘야 합니다.
- const char* hostname: 변환하고자 하는 도메인 이름
// 성공 시 hostent 구조체 변수의 주소 값, 실패 시 NULL 포인터 반환
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
반환되는 hostent 구조체는 다음과 같이 정의되어 있습니다.
struct hostent
{
char* h_name; // official name
char** h_aliases; // alias list
int h_addrtype; // host address type
int h_length; // address length
char** h_addr_list; // address list
}
복잡하게 보일 수도 있는데, 도메인 이름을 IP로 변환하는 경우에는 그냥 h_addr_list만 신경쓰면 됩니다. 간단히 각 멤버에 대해 소개하겠습니다.
- char* h_name: 이 멤버에는 공식 도메인 이름이 저장됩니다. 공식 도메인 이름은 해당 홈페이지를 대표하는 도메인 이름이라는 의미를 갖지만, 실제 우리에게 잘 알려진 유명 회사의 도메인 이름이 공식 도메인 이름으로 등록되지 않은 경우가 많습니다.
- char** h_aliases: 같은 메인 페이지인데도 다른 도메인 이름으로 접속할 수 있는 경우가 있습니다. 하나의 IP에 둘 이상의 도메인 이름을 지정하는 것이 가능해서, 공식 도메인 이름 이외에 해당 메인 페이지에 접속할 수 있는 다른 도메인 이름의 지정이 가능합니다. 이들 정보는 h_aliases에서 확인할 수 있습니다.
- int h_addrtype: 이 함수는 IPv4와 IPv6 모두를 지원합니다. 때문에 h_addr_list로 반환된 IP 주소의 주소체계에 대한 정보를 이 멤버를 통해 반환하는데, IPv4의 경우 이 멤버에 AF_INET이 저장됩니다.
- int h_length: IP 주소의 크기정보가 담깁니다. IPv4의 경우에는 4가 저장되고, IPv6의 경우에는 16이 저장됩니다.
- char** h_addr_list: 도메인 이름에 대한 IP 주소가 정수의 형태로 반환됩니다. 참고로 접속자수가 많은 서버는 하나의 도메인 이름에 대응하는 IP를 여러 개 둬서 둘 이상의 서버로 부하를 분산시킬 수 있는데, 이 경우에도 이 멤버를 통해 모든 IP 주소정보를 알 수 있습니다.
아래 그림은 이 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[]) {
struct hostent* host;
if (argc != 2) {
printf("Usage: %s <addr>\n", argv[0]);
exit(1);
}
host = gethostbyname(argv[1]);
if (!host)
error_handling("gethostbyname error");
printf("Official name: %s\n", host->h_name);
for (int 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 (int 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);
}
결과는 아래와 같습니다.
그런데 h_addr_list를 이용하는 과정에서 알 수 없는 형변환이 있는 것을 볼 수 있습니다. 구조체 hostent의 정의 부분만 놓고 보면 h_addr_list가 가리키는 것은 문자열 포인터 배열입니다. 그러나 문자열 포인터 배열의 각 요소가 실제 가리키는 것은 문자열의 주소값이 아닌 in_addr 구조체 변수의 주소값입니다. 구조체 hostent는 IPv4만을 위해 정의된 구조체가 아니므로 h_addr_list가 가리키는 배열에 IPv6 기반의 주소 정보도 저장될 수 있도록 char형 포인터 배열로 선언한 것입니다.
IP 주소를 이용해 도메인 정보 얻어오기
이번에 소개할 gethostbyaddr 함수는 IP 주소를 이용해 도메인 정보를 얻을 때 사용하는 함수입니다. 이 함수에는 다음과 같은 인자들을 전달해줘야 합니다.
- const char* addr: IP 주소를 지니는 in_addr 구조체 변수의 포인터 전달, IPv4 이외의 다양한 정보를 전달받을 수 있도록 일반화하기 위해서 매개변수를 char형 포인터로 선언
- socklen_t len: 첫 번째 인자로 전달된 주소정보의 길이, IPv4의 경우 4, IPv6의 경우 16 전달
- int family: 주소체계 정보 전달, IPv4의 경우 AF_INET, IPv6의 경우 AF_INET6 전달
// 성공 시 hostent 구조체 변수의 주소 값, 실패 시 NULL 포인터 반환
#include <netdb.h>
struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);
이를 이용하는 간단한 예제를 살펴보겠습니다.
#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[]) {
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("gethostbyname error");
printf("Official name: %s\n", host->h_name);
for (int 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 (int 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);
}
결과는 아래와 같습니다.
'네트워크 프로그래밍 > C' 카테고리의 다른 글
[네트워크 프로그래밍/C] 소켓의 다양한 옵션 (0) | 2022.02.28 |
---|---|
[네트워크 프로그래밍/C] Domain Name System (0) | 2022.02.26 |
[네트워크 프로그래밍/C] TCP 기반의 Half-close (0) | 2022.02.26 |
[네트워크 프로그래밍/C] UDP의 데이터 송수신 특성과 UDP에서의 connect 함수호출 (0) | 2022.02.26 |
[네트워크 프로그래밍/C] UDP 기반 서버, 클라이언트의 구현 (0) | 2022.02.25 |