본문 바로가기

[ Windows Program ]/Windows API

주소 사용법( 주소체계, 바이트순서 )

이번 단원은 네트웍 프로그래밍을 하기 위해서는 꼭 이해하고 넘어가야 하는 부분입니다. 중요한 만큼 최대한 자세히 다루려고 합니다. 시작해 볼까요?

 

우리가 이번 단원에서 배울내용은 크게 나누어서 다음과 같습니다. 무슨 내용을 배울때 어떤 내용이 핵심내용인지 파악하고 있다면 훨씬 수월하게 이해할 수 있겠죠.

 

1. IP와 port

2. sockaddr_in 구조체

3. 네트워크 바이트 순서( Network byte order )

4. 주소 사용방법.

 

 

 

1. IP와 port에 대해서..

IP주소 모르시는분 없겠죠? 이에대해 굳이 정의를 해본다면 인터넷에 존재하는 사용자(호스트)들을 구분하기 위한 32비트 주소체계를 의미합니다. 이를 다른말로 IPv4(4자리의 IP)라고도 하죠. 지금 도스창에서 ipconfig를 실행해 보세요. 그럼 다음과 같은 부분이 나타날 겁니다.

 

...

 

IP Address. . . . . . . . . . . . . . . . : 211.61.xxx.xx

 

...

 

이것이 바로 IP입니다. 여기선 4자리니까 IPv4방식을 사용하는걸 알 수 있겠죠? 현재는 대부분 IPv4방식의 아이피를 사용하고 있습니다. 

 

그럼 IP는 뭘까요? 쉽게 말해서 사용자를 구분하기 위해서 사용하는 일종의 번호표 입니다. 우리 학교다닐때 출석체크 많이 했었죠? 이때 이름을 부르기도 했지만 학생의 번호를 부르기도 했을겁니다. 1번 왔나? 2번 왔나? 이런식으로 말이죠. 쉽게 말해 IP도 이와같은 겁니다. 인터넷에 접속해 있는 많은 사용자들을 컴퓨터가 쉽게 구분시키기 위해 각각의 접속자마다 고유의 번호를 붙여주는 거죠. 당연히 중복되는 IP를 가질 수는 없겠죠? 이게 IP내용의 전부는 아니지만 일단 이정도만 알아도 크게 지장은 없습니다. 하지만 많이 알면 알고 있을수록 자신에게 득이되면 되었지 해가되지는 않아요. 따로 문서를 봐두는 것도 좋습니다.

 

 

그럼 port는 무엇일까요?

 

우리는 컴퓨터를 사용할때 여러 프로그램을 켜놓고 이리저리 왔다갔다 하면서 프로그램을 사용합니다. 만약 채팅프로그램과 파일전송프로그램 두개를 켜놓고 있다고 가정해 봅시다. 채팅프로그램과 파일전송 프로그램은 각각 따로 연결되어 있는 상태겠지요? 채팅프로그램은 A라는 서버에 연결되어 있고 파일전송 프로그램은 B라는 서버와 연결된 상태일 것입니다.

 

A라는 서버는 우리의 컴퓨터에 데이터를 보내기 위해 IP를 사용한다고 했습니다. 맞습니다. 그럼 IP를 사용해서 A라는 서버는 우리에게 채팅관련 데이터를 보낼것이고 B라는 서버도 IP를 사용해 우리에게 파일관련 데이터를 보낼것입니다. 이해 되시죠? 그런데...

 

어떻게 이 데이터들이 충돌하지 않을까요? 똑같은 IP주소에 각각 다른 데이터를 받고 있는데 이게 충돌하지 않고 각각의 응용프로그램에 잘 전달되는걸 알 수 있습니다. 채팅데이터는 채팅프로그램에 들어가고 파일데이터는 파일전송 프로그램에 잘 전송된다는 말입니다. 어떻게 이게 가능할까요?

 

이게 바로 port가 하는 일입니다. 쉽게 말해서 port도 하나의 주소입니다. 이해를 돕기위해 편지라는 예를 들어보겠습니다. 우리가 편지를 보낼때 받는사람 부분에 크게 2가지의 정보를 적습니다. 하나는 주소, 또 하나는 받는 사람의 정보를 기재하죠. 이를 IP와 port와 연관지어 생각할 수 있습니다. 주소는 IP에.. 받는이는 port에 말예요. 만약 우리가 주소는 적었는데 받는이를 적지 않았다면 어떻게 되겠습니까? 그 집에 여러사람이 살고 있다면 '이 편지가 나한테 온건가?'하고 먼저 그 편지를 본사람이 뜯어보겠죠?

 

위와같은 충돌을 피하기 위해서 port라는게 필요한 겁니다. 정리해 보자면 IP는 인터넷의 수많은 사용자들을 구분하기 위해 있는것이고 port라는 것은 IP에 해당하는 사용자가 사용하고 있는 프로그램중 어떤 프로그램에 데이터를 전송해야하는지 구분하기 위해 있는 겁니다.

 

결론적으로.. 우리가 어떤 호스트에게 데이터(이를 다른말로 패킷이라고 합니다)를 보내기 위해선 데이터내에 IP주소와 port를 함께 포함시켜 주어야 합니다. 이점 잊지 마세요.

 

 

 

 

 

2. sockaddr_in 구조체

이제 본격적으로 IP와 port를 사용할 차례입니다. 이를 사용하기 위해 sockaddr_in이라는 인터넷 기반( IPv4 )에서 사용되는 구조체가 있습니다. 한번 살펴볼까요?

 

 

struct sockaddr_in {
        short   sin_family;               /* 주소체계( address family ) */
        u_short sin_port;               /* 16비트 TCP / UDP Port */
        struct  in_addr sin_addr;    /* 32비트 IPv4 주소 */
        char    sin_zero[8];             /* 사용되지 않음 */
};

 

 

총 4개의 멤버가 있지만 맨 마지막은 sockaddr 구조체와의 호환성을 유지하기 위한 바이트를 맞추기 위해 사용되는.. 우리는 앞으로 전혀 쓸일이 없는 멤버입니다. 그럼 왜 굳이 sockaddr구조체와 sockaddr_in구조체로 나누어 놓았을까요? 이유는 확실하게 알수는 없지만 아무래도 IP와 port를 쉽게 사용하기 위해 구분해 놓았다는 말들을 합니다. 실제로도 sockaddr_in이 IP와 port를 사용하기 쉽게 되어 있구요. 하지만 중요한점!!

 

sockaddr구조체는 여러 주소체계에서 가능하지만 sockaddr_in구조체는 인터넷기반(IPv4)에서만 사용할 수 있다는 점을 주의해야 합니다. 소켓프로그램은 인터넷기반만 있는것이 아니니 sockaddr구조체가 있는건 어쩌면 당연한 거지요.

 

구조체를 보면 생소한게 하나 나와있습니다. 바로 "주소체계"라고 하는 건데 이는 쉽게 생각해서 모든 프로토콜은 자신만의 고유한 주소 포멧이 있습니다. 예를 들어 IPv4에서는 32비트 주소체계를 사용하지만 IPv6에서는 128비트 주소체계를 사용합니다. 각각 프로토콜에 따라 주소 정보를 나타내는 데이터타입(Data type)들이 따로 존재하는 것이죠.

 

 

아래는 sockaddr_in 구조체의 각각의 멤버에 대한 설명입니다.

 

sin_family :

프로토콜 체계마다 주소체계가 다르다고 하였습니다. 이 멤버에는 사용하는 주소 체계에 대한 정보를 넣어주면 됩니다. 아래 목록을 사용할 수 있는 주소체계 값들 입니다.

 

AF_INET : IPv4 인터넷 프로토콜

AF_INET6 : IPv6 인터넷 프로토콜

AF_LOCAL : Local 통신을 위한 UNIX프로토콜

 

 

sin_port :

16비트 Port 정보를 대입해 줍니다( 네트웍 바이트 순서 )

 

sin_addr :

32비트 IP정보를 대입해 줍니다( 네트웍 바이트 순서 )

 

sin_zero :

단지 sockaddr구조체와의 바이트를 맞추기 위해(호환성을 위해) 사용되는 멤버.

이 값은 사용하지 않음.

 

 

 

 

 

 

 

3. 네트워크 바이트 순서( Network byte order )

sockaddr_in 구조체 멤버에 값을 대입하기 위해선 먼저 대입할 값을 네트워크 바이트 순서로 변경한다음 대입시켜주어야 합니다. 그럼 네트웍 바이트 순서란 뭘 의미하는 걸까요?

 

인터넷을 사용하는 전세계 인구중에 각각 바이트를 표현하는 CPU가 다릅니다.

예를들어 Intel계열의 CPU가 처리하는 바이트 방식과 Motorola계열의 CPU가 처리하는 바이트 방식은 정반대지요. 만약에 Intel계열을 사용하는 호스트가 Motorola계열의 호스트에게 데이터를 보냈다고 해봅시다. 어떻게 될까요? 보낸데이터를 거꾸로 읽어버리게 되겠죠? 완전 뒤죽박죽이 되어버리는 겁니다. 네트웍 상에서 반드시 같은 계열의 CPU를 가진 사람끼리만 접속되는게 아닙니다. 다른 계열의 CPU사용자와 데이터를 주고 받을 수 있다는 말이죠.

 

이런 심각한 오류를 범하지 않기 위해서 사용하는게 "네트워크 바이트 순서"입니다.

이는 어떤 계열의 CPU를 사용하더라도 "네트워크 바이트 순서"라는 동일한 바이트 순서대로 데이터를 관리하기 때문에 위와같은 충돌이 생기지 않도록 해줍니다. 그럼 네트워크 바이트 순서대로 어떻게 대입을 하는걸까요? 이제부터 그걸 알아보도록 합니다.

 

네트웍 바이트 순서로 변환시켜주는 함수들이 존재합니다. 아래는 함수 목록입니다.

 

unsigned short htons ( unsigned short ); /* 호스트 순서 -> 네트웍 순서 */

unsigned short ntohs ( unsigned short ); /* 네트웍 순서 -> 호스트 순서 */

 

unsigned long htonl ( unsigned long );    /* 호스트 순서 -> 네트웍 순서 */

unsigned long ntohl ( unsigned long );    /* 네트웍 순서 -> 호스트 순서 */

 

이름이 htons, ntohs, htonl, ntohl 이런식으로 되어 있는데 헷갈리기 딱 좋게 되어 있죠?

이를 혼동하지 않기 위해 약간 부연설명을 드리겠습니다.

 

h : host byte order(호스트 바이트 순서)  로 해석

n : network byte order( 네트워크 바이트 순서 ) 로 해석

s : short 형으로 해석

l  : long  형으로 해석

 

그럼 htons와 htonl을 살펴보면 간단히 해석이 되네요

htons는 'short형의 호스트 바이트 순서의 데이터를 네트워크 바이트 순서로 변환'

htonl은  'long형의 호스트 바이트 순서의 데이터를 네트워크 바이트 순서로 변환'

 

어렵지 않군요. 우리가 다른 호스트에게 정보를 보내기 위해선 hton형식으로 변환해서 보내고

다른 호스트에게서 정보를 받을땐 ntoh형식으로 변환해서 읽어들이면 되겠군요. 너무 쉽다~

 

 

 

 

 

 

 

4. 주소사용 방법

sockaddr_in 구조체 멤버중 주소를 나타내는 멤버(sin_addr)에 값을 대입하는데 그냥 대입해선 안되겠죠? 만약 211.61.xx.xxx 라는 주소를 대입한다고 하면 먼저 네트웍 바이트 순서로 변환을 시킨다음 대입해야 한다고 했습니다. 하지만 주소는 조금 특별합니다. 그게 끝이 아니죠.

 

주소를 대입할땐 unsigned long 형으로 대입을 해야 합니다.

211.61.xx.xxx는 십진수 표현방식(Dotted-Decimal Notation)으로 되어 있죠.

그래서 우리는 인터넷 주소를 조작해주는 함수들을 사용해서 정보를 대입해주어야 합니다. 아래 나와있는 함수는 Dotted-Decimal Notation방식의 주소값을 unsigned long 방식으로 변환해줄 뿐만 아니라 네트워크 바이트 순서로의 변환도 알아서 해줍니다.

 

 

unsigned long inet_addr( const char *string );

 

성공시 unsigned long형의 32비트 값, 에러시 INADDR_NONE 리턴

 

 

string :

여기 인자 값으로 Dotted-Decimal Notation 문자열의 포인터를 넘겨주게 되면,

해당하는 unsigned long 타입의 데이터 값을 리턴

 

 

 

 

 

 

 

아래 예제는 간단하게 주소를 네트웍 바이트 순서로 변환해서 출력하는 예제입니다.

한번 직접 컴파일해서 실행해 보세요.

이 프로그램을 컴파일 하기 전에 반드시 ws2_32.lib 을 링크시켜 주어야 합니다.

 

 

#include <stdio.h>

#include <winsock.h>


void  main( void )

{

     WSADATA wsaData;

     char * addr1 = "1.2.3.4";

     char * addr2 = "1.2.3.256";

     unsigned long conv_addr;

 

     /* 윈속을 초기화 하는 코드 */

     if ( WSAStartup( MAKEWORD(2, 2), &wsaData ) != 0 ) {
          printf( "WSAStartup error!" );
          exit( 1 );
     }

 

 

     /* 1.2.3.4주소를 unsigne long형으로 변환 */

     conv_addr = inet_addr( addr1 );

     if ( conv_addr == INADDR_NONE )

          printf( "Error \n" );

     else

          printf( "Unsigned long addr ( network byte order ) : %x \n", conv_addr );

 

 

     /* 1.2.3.256 은 잘못된 주소이므로 에러를 리턴할 것임 */

     conv_addr = inet_addr( addr2 );

     if ( conv_addr == INADDR_NONE )

          printf( "Error \n" );

     else

          printf( "Unsigned long addr ( network byte order ) : %x \n", conv_addr );

}     


- Reference

  http://cafe.naver.com/nevernding