인터넷 주소 (Internet Address)
인터넷 상에서 컴퓨터를 구분하는 목적으로 사용되는 주소이며, IPv4 (4바이트) 와 IPv6 (16바이트) 가 있다. 인터넷 주소는 네트워크 주소와 호스트 주소로 나뉘는데, 네트워크 주소로 해당 네트워크를 찾은 후 호스트 주소를 이용해서 호스트를 구분해준다. 예를 들어 IPv4를 보자. IPv4는 아래와 같이 4개의 클래스로 나뉘며, 진한 부분이 네트워크 주소, 연한 부분이 호스트 주소가 되겠다.
클래스 A
00000000 00000000 00000000 00000000
클래스 B
00000000 00000000 00000000 00000000
클래스 C
00000000 00000000 00000000 00000000
클래스 D
00000000 00000000 00000000 00000000
이 때 클래스 A의 첫번째 바이트 범위는 1 ~ 127, 클래스 B의 첫번째 바이트 범위는 128 ~ 191, 클래스 C의 첫번째 바이트 범위는 192 ~ 223, 클래스 D는 그 외가 되는데, 그렇다면 클래스 A에선 00000001 ~ 0111111 이므로 무조건 0으로 시작해야되고, 클래스 B는 10000000 ~ 10111111 이므로 무조건 10으로 시작 되어야하며, 클래스 C는 11000000 ~ 11011111 이므로 110으로 시작되어야 한다. 이 처럼 첫번째 바이트만 봐도 클래스 구분을 할 수 있게 된다.
PORT
IP가 어떤 컴퓨터인지 구분할 수 있게 해준다면, PORT는 어떤 소켓인지 구분할 수 있게 해주는 것이다. 물론 한 프로그램에서 생성되는 소켓은 여러 개가 존재할 수 있으므로 하나의 프로그램에 여러 개의 포트가 할당될 수 있다. PORT 번호는 16비트 (0 ~ 65535) 로 생성되며 그 중 0 ~ 1023 은 잘 알려진 포트 (Well-Known Port) 로 용도가 결정되어 있는 포트다.
IPv4 기반에서 IP와 PORT를 담아 소켓에 주소를 할당하는 구조체는 아래와 같다.
#include <sys/socket.h> struct sockaddr_in{ sa_family_t sin_family; // 주소 체계 u_int16_t sin_port; // PORT 번호 struct in_addr sin_addr; // 32비트 IP주소 char sin_zero[8]; }; struct in_addr{ in_addr_t s_addr; // 32비트 IPv4 인터넷 주소 };
바이트 순서와 네트워크 바이트 순서
바이트 순서 저장 방식에는 빅 엔디안 (Big Endian) 과 리틀 엔디안 (Little Endian) 이 있다. 빅 엔디안은 상위 바이트의 값을 작은 번지수에 저장하는 것이며, 리틀 엔디안은 반대로 상위 바이트의 값을 큰 번지수에 저장한다. 가령 0x12345678 을 빅 엔디안과 리틀 엔디안으로 저장한다면 아래와 같이 저장하게 된다.
빅 엔디안 (Big Endian)
0x20번지 0x21번지 0x22번지 0x23번지
0x12 0x34 0x56 0x78
리틀 엔디안 (Little Endian)
0x20번지 0x21번지 0x22번지 0x23번지
0x78 0x56 0x34 0x12
호스트 바이트 순서는 CPU 마다 저장하는 방식이 다른데, 가령 인텔의 경우 리틀 엔디안 방식으로 연산 속도를 높였는데, 따라서 인텔 계열은 어떤 데이터를 받게 되면 리틀 엔디안 방식으로 변환해줘야 한다.
네트워크 바이트 순서는 빅 엔디안 방식이 기준이다. 만약 리틀 엔디안 방식으로 0x1234를 송신할 경우 0x4321로 받게 되지만 빅 엔디안 방식일 경우 0x1234 -> 0x1234 그대로 받게 된다. 이 처럼 데이터 송수신 과정에서 문제가 발생할 수 있으므로 통일된 기준을 사용한다.
바이트를 변환하는 함수에는 아래 4가지가 있다. h는 호스트 바이트 순서를, n은 네트워크 바이트 순서를, s는 short, l은 long을 의미한다. 즉 예를 들어 htons 의 경우는 호스트 바이트 순서를 네트워크 바이트 순서로 변환하되 short 형으로 변환하는 것이다.
#include <sys/socket.h> unsigned short htons(unsigned short); unsigned short ntohs(unsigned short); unsigned long htonl(unsigned long); unsigned long ntohl(unsigned long);
그런데 보통 IP 주소를 보게 되면 211.214.107.99 와 같이 .으로 구분된 주소를 보게 되는데, 우리가 처리하고 싶은 형식은 32비트 정수형이다. 위의 string 형식을 32비트 정수형으로 반환 해주는 함수가 있다. 이 함수로 올바른 string 형식이 들어오게 되면 32비트 형식을 출력해주고, 실패 시에는 INADDR_NONE을 반환하게 된다.
#include <arpa/inet.h> in_addr_t inet_addr(const char * string);
아래 코드를 보자. addr1에는 211.214.107.99가, addr2에는 1.2.3.256 이라는 주소가 들어있다. 실행한다면 둘 다 적당한 32비트 정수형으로 반환할 것 같지만 아래와 같은 결과가 나오게 된다.
#include <sys/socket.h> #include <arpa/inet.h> #include <iostream> using namespace std; int main(int argc, char *argv[]){ char *addr1 = "211.214.107.99"; char *addr2 = "1.2.3.256"; unsigned long conv_addr = inet_addr(addr1); if(conv_addr == INADDR_NONE){ cout << "ERROR OCCURED" << '\n'; } else printf("NETWORK ORDERED INTEGER ADDR : %#lx \n", conv_addr); conv_addr = inet_addr(addr2); if(conv_addr == INADDR_NONE){ cout << "ERROR OCCURED" << '\n'; } else printf("NETWORK ORDERED INTEGER ADDR : %#lx \n", conv_addr); return 0; }
전자는 기대한 결과가 출력되지만 후자는 에러가 발생한다. 왜 그럴까?
32비트라고 하면 보통 아래와 같은 형식이다.
00000000 00000000 00000000 00000000
즉 1, 2, 3, 256 마다 8비트씩 사용해야한다는 얘긴데, 1, 2, 3 같은 경우는 8비트로 표현이 가능하지만, 8비트로 표현할 수 있는 최대 수는 255기 때문에 256은 표현할 수 없게 된다. 그 결과 에러가 발생하게 되는 것이다.
inet_addr 와 같은 기능을 하는 함수가 또 있다. inet_aton 역시 적당한 string을 32비트 정수형으로 변환해주는데, 차이점은 뒤의 매개변수에 들어가는 구조체에 변환 결과가 저장이 된다.
#include <arpa/inet.h> int inet_aton(const char * string, struct in_addr * addr);
이와 반대로 32비트 정수형을 위에서 봤던 string 형식으로 변환해주는 함수가 있다. inet_ntoa 에 주소 정보가 저장된 구조체를 넣어 해당 구조체에 저장된 32비트 형식의 IP주소를 1.2.3.4 와 같은 형식으로 변환해주는데, 정상적으로 변환되지 않을 경우 -1을 출력하게 된다.
#include <arpa/inet.h> char * inet_ntoa(struct in_addr adr);
이 밖에도 임의의 IP주소를 직접 입력해서 변환하는 게 아닌 현재 컴퓨터의 IP주소를 변환하고자 한다면 INADDR_ANY 라는 변수를 htonl 에 인자 값으로 넣어 그대로 사용할 수도 있다.
댓글
댓글 쓰기