책정리/윈도우 네트워크 프로그래밍

3장 소켓 주소 구조체 다루기

GONII 2015. 4. 11. 19:49
  • 소켓 주소 구조체 정의

    소켓 주소 구조체(socket address structures)는 네트워크 프로그램에서 필요로 하는 주소 정보를 담고 있는 구조체로, 다양한 소켓 함수의 인자로 사용한다. 여러 소켓 주소 구조체 중 가장 기본이 되는 것은 SOCKADDR로 구조체 정의는 다음과 같다

    typedef struct sockaddr {

    u_short        sa_family;

    CHAR        sa_data[14];

    } SOCKADDR, *PSOCKADDR, *LPSOCKADDR;

    • sa_family

      주소 체계를 나타내며, 부호 없는 16비트 정수값을 사용한다. 예를 들어, TCP/IP프로토콜을 사용한다면 이 값은 AF_INET이 된다.

    • sa_data[14]

      해당 주소 체계에서 사용하는 주소 정보를 담고 있다. 주소 체계에 따라 필요한 정보가 다르므로 가장 일반적인 형태인 바이트 배열로 선언한 것이다. TCP/IP 프로토콜을 사용한다면 IP주소와 포트 번호가 저장될 것이다.

    실제 프로그래밍에서는 애플리케이션이 사용할 프로토콜의 종류에 맞는 소켓 주소 구조체를 사용한다. 예를 들어 TCP/IP에서는 SOCKADDR_IN, InDA에서는 SOCKADDR_IRDA를 사용한다. TCP/IP프로토콜(IPv4)을 위한 소켓 주소 구조체 정의는 다음과 같다

    typedef struct sockaddr_in

    {

    short        sin_family;

    USHORT        sin_port;

    IN_ADDR        sin_addr;

    CHAR        sin_zero[8];

    } SOCKADDR_IN, *PSOCKADDR_IN;

    • sin_family

      주소 체계를 나타내며, 부호 있는 16비트 정수값을 사용한다. 항상 AF_INET 값을 사용한다.

    • sin_port

      포트 번호를 나타내며, 부호 없는 16비트 정수값을 사용한다.

    • sin_addr

      IP 주소를 나타내며, 32비트 in_addr 구조체를 사용한다.

    • sin_zero[8]

      실제로 사용하지는 않으나 항상 0으로 채우도록 한다.

    IP주소를 저장하기 위한 in_addr 구조체 정의는 다음과 같다. 동일한 메모리 영역을 각각 8비트(S_un_b), 16비트(S_un_w), 32비트(S_addr) 단위로 접근할 수 있게 만든 공용체(S_un)임을 알 수 있다. 대개 32비트 단위로 접근하므로 S_un.Saddr 필드를 사용하게 되는데, 매크로를 이용하여 재정의한 s_addr을 사용하면 편리하다.

    typedef struct in_addr {

    union{

    struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;

    struct { USHORT s_w1,s_w2; } S_un_w;

    ULONG S_addr;

    } S_un;

    #define s_addr S_un.S_addr

    } IN_ADDR, *PIN_ADDR, *LPIN_ADDR;

    지금까지 살펴본 SOCKADDR과 SOCKADDR_IN 구조체를 각 필드의 크기를 기준으로 비교하면 다음과 같다

    소켓 주소 구조체는 크기가 크기 때문에 소켓 함수 인자로 사용할 때는 항상 주소값을 전달하며, 이때 반드시 SOCKADDR 포인터형으로 변환(type casting)한다.

    소켓 주소 구조체를 사용하는 예는 다음과 같다.

    • 소켓 주소 구조체를 초기화한 후 소켓 함수에 넘겨주는 경우

      SOCKADDR_IN addr1;

      // 소켓 주소 구조체를 초기화한다.

      ....

      f((SOCKADDR*)&addr1,...);

    • 소켓 함수가 소켓 주소 구조체를 입력으로 받아서 초기화하고, 애플리케이션이 이를 출력 등의 목적으로 사용하는 경우

      SOCKADDR_IN addr2;

      g((SOCKADDR*)&addr2, ...);

      // 소켓 주소 구조체를 사용한다.

  • 바이트 정렬 함수

    바이트 정렬(byte ordering)이란 메모리에 데이터를 저장할 때의 바이트 순서를 나타내는 용어로, 빅 엔디안(big-endian)과 리틀 엔디안(little-endian) 방식이 있다. 빅 엔디안은 최상위 바이트(MSB, Most Significant Byte)부터 차례로 저장되는 방식이며, 리틀 엔디안은 최하위 바이트(LST, Least Significant Byte)부터 차례대로 저장되는 방식이다.

    16진수 0x12345678을 메모리 0x1000번지에 저장할 때 두 바이트 정렬 방식의 차이를 보여준다.

    파일에 데이터를 저장하고 읽어오는 경우나 네트워크를 통해 데이터를 전송하는 경우, 바이트 정렬 방식에 특히 유의해야 한다.

    네트워크 애플리케이션에서 바이트 정렬 방식을 고려해야 하는 경우를 두 종류로 구분하여 살펴보자.

    • IP 주소, 포트 번호 등과 같이 프로토콜 구현을 위해 필요한 정보

      (a)는 IP주소의 바이트 정렬 방식에 따른 문제점을 보인 것이다. 종단 시스템이 보낸 패킷의 IP 헤더에는 IP주소가 포함되어 있고, 이 패킷을 라우터가 받으면 IP 주소를 참조하여 다음 위치의 라우터에 보낸다. 이때 종단 시스템과 라우터가 IP주소의 바이트 정렬을 약속하지 않을 경우, IP 주소 해석이 달라져서 잘못된 라우팅이 이루어질 가능성이 있다.

      (b)는 포트 번호의 바이트 정렬 방식에 따른 문제점을 보인 것이다. 두 종단 시스템이 포트 번호의 바이트 정렬을 약속하지 않을 경우, 잘못된 프로세스에 데이터가 전달될 가능성이 있다. 이런 문제점 때문에 포트 번호의 바이트 정렬 방식은 빅 엔디안으로 통일하여 사용하도록 되어 있다. 네트워크 용어로 빅 엔디안을 네트워크 바이트 정렬(network byte ordering)이라고 부른다. 반면 시스템이 사용하는 고유한 바이트 정렬 방식을 호스트 바이트 정렬(host byte ordering)이고 부른다.

    • 애플리케이션이 주고 받는 데이터

      (c)는 데이터 정렬 방식에 따른 문제점을 보인 것이다. 두 종단 시스템이 교환하는 데이터에 대해 바이트 정렬 방식을 약속하지 않으면, 데이터 해석의 문제가 발생할 수 있다. 서버와 클라이언트를 같이 작성하는 경우라면 바이트 정렬 방식을 둘 중 하나로 통일해서 사용하는데, 대개 네트워크 바이트 정렬(빅 엔디안) 방식을 사용한다. 클라이언트만 작성한다면, 기존의 서버가 데이터에 대해 정한 바이트 정렬 방식을 따라야 한다.

    윈속에서는 바이트 정렬을 위한 유틸리티 함수를 지원한다. hton*( ) 함수는 호스트 바이트 정렬로 저장된 값을 입력 받아 네트워크 바이트 정렬로 변환한 값을 리턴한다. ntoh*( )함수는 네트워크 바이트 정렬로 저장된 값을 입력으로 받아서 호스트 바이트 정렬로 변환한 값을 리턴한다.

    u_short htons(u_short hostshort); // host-to-network-short

    u_long htonl(u_long hostlong); // host-to-network-long

    u_short ntohs(u_short netshort); // network-to-host-short

    u_long ntohl(u_long netlong); // network-to-host-long

    일반적으로 hton*( )함수는 애플리케이션이 소켓 함수에 값을 넘겨주기 전에 호출하며, ntoh*( )함수는 소켓 함수가 결과로 전달한 값을 애플리케이션에서 출력 등의 목적으로 사용하기 전에 호출한다.

    윈속 2.x에서는 바이트 정렬을 위해 다음과 같은 확장 함수를 지원한다. 첫번째 인자로 소켓 디스크립터를 사용하고, 변환된 결과를 함수 리턴값이 아닌 세 번째 인자로 전달한다는 차이가 있다.

    int WSAHtons(SOCKET s, u_short hostshort, u_short* lpnetshort);

    int WSAHtonl(SOCKET s, u_long hostlong, u_long* lpnetlong);

    int WSANtohs(SOCKET s, u_short netshort, u_short* lphostshort);

    int WSANtohl(SOCKET s, u_long netlong, u_long* lphostlong);

    SOCKADDR_IN 구조체는 다음과 같은 바이트 정렬 방식을 사용한다.

    • 예제 ByteOrder

#include <winsock2.h>

#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

   

int main(int argc, char* argv[])

{

WSADATA wsa;

if( WSAStartup(MAKEWORD(2,2), &wsa) != 0 )

{

return -1;

}

   

u_short x = 0x1234;

u_long y = 0x12345678;

   

u_short x2;

u_long y2;

   

// 호스트 바이트 -> 네트워크 바이트

printf("호스트 바이트 -> 네트워크 바이트\n");

printf("0x%x -> 0x%x\n", x, x2 = htons(x));

printf("0x%x -> 0x%x\n\n", y, y2 = htonl(y));

   

// 네트워크 바이트 -> 호스트 바이트

printf("네트워크 바이트 -> 호스트 바이트\n");

printf("0x%x -> 0x%x\n", x2, ntohs(x2));

printf("0x%x -> 0x%x\n\n", y2, ntohl(y2));

   

// 잘못 된 사용 예

printf("잘못된 사용 예\n");

printf("0x%x -> 0x%x\n", x, htonl(x));

   

WSACleanup();

   

return 0;

}

  • IP 주소 변환 함수

    윈도우 애플리케이션에서는 IP주소를 입력받기 위해 명령행 인자를 사용하거나 에디트 컨트롤(edit control)을 이용하는 경우가 종종 있다. 이때 애플리케이션은 IP주소를 문자열 형태로 전달받게 되므로 이를 32비트 숫자로 변환해야 한다.

    윈속에서는 IP주소 변환을 위해 다음과 같은 함수를 지원한다. inet_addr( )함수는 문자열 형태로 IP주소를 입력받아 32비트 숫자(네트워크 바이트 정렬)로 리턴한다. inet_ntoa( )함수는 32비트 숫자(네트워크 바이트 정렬)로 IP 주소를 입력받아 문자열 형태로 리턴한다.

    unsigned long inet_addr(const char* cp);

    char* inet_ntoa(struct in_addr in); // network-to-ascii

    바이트 정렬 함수와 IP주소 변환 함수를 SOCKADDR_IN 구조체에 적용하는 예는 다음과 같다

    • 소켓 주소 구조체를 초기화한 후 소켓 함수에 넘겨주는 경우

      // 소켓 주소 구조체 초기화

      SOCKADDR_IN addr;

      ZeroMemory(&addr, sizeof(addr)); // 0으로 초기화

      addr.sin_family = AF_INET;

      addr.sin_addr.s_addr = inet_addr("147.46.114.70");

      addr.sin_port = htons(9010);

         

      // 소켓 함수 호출

      f((SOCKADDR*)&addr, ...);

    • 소켓 함수가 소켓 주소 구조체를 입력받아서 값을 채우면, 애플리케이션이 이를 출력 등의 목적으로 사용하는 경우

      // 소켓 주소 구조체 선언

      SOCKADDR_IN addr;

         

      // 소켓 함수 호출

      g((SOCKADDR*)&addr, ...);

         

      // IP주소와 포트 번호 출력

      printf("IP주소 = %s, 포트번호 = %d\n",

      inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));

    • 예제 IPAddr

#include <winsock2.h>

#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

   

int main(int argc, char* argv[])

{

WSADATA wsa;

if( WSAStartup(MAKEWORD(2,2), &wsa) != 0 )

{

return -1;

}

   

// 원래의 IP 주소 출력

char* ipaddr = "147.46.114.70";

printf("IP주소=%s\n", ipaddr);

 

// inet_addr() 함수 연습

printf("IP 주소(변환 후) = 0x%x\n", inet_addr(ipaddr));

   

// inet_ntoa() 함수 연습

IN_ADDR temp;

temp.s_addr = inet_addr(ipaddr);

printf("IP 주소(변환 후) = %s\n", inet_ntoa(temp));

   

WSACleanup();

   

return 0;

}

  • 도메인 이름 시스템과 이름 변환 함수

    도메인 이름(domain name)이란 IP주소와 대응하는 이름으로, 사람이 기억해서 사용하기 쉽게 만든 것이다. TCP/IP 프로토콜은 도메인 이름을 인식하지 못하므로 사용자가 입력한 도메인 이름을 반드시 IP주소로 변환해야 한다.

    ping 유틸리티에 입력한 도메인 이름이 IP주소로 변환되는 것을 볼 수 있다

    IP주소와 도메인 이름 사이의 변환 정보는 인터넷에 연결된 여러 개의 도메인 이름 서버(DNS Server, Domain Name System Server)가 관리하며, 어느 한 도메인 이름 서버가 모든 정보를 가지고 있지 않다는 점에서 일종의 분산 데이터베이스(distributed database)라고 할 수 있다.

    윈속에서는 IP주소와 도메인 이름 사이의 변환을 위해 다음과 같은 함수를 제공한다.

    // 도메인이름-> IP주소(네트워크바이트정렬)

    struct hostent* gethostbyname(const char* name);

       

    // IP주소(네트워크바이트정렬) -> 도메인이름

    struct hostent* gethostbyaddr(

    const char* addr, // 네트워크바이트정렬된IP주소

    int len, // IP주소의길이(예:4)

    int type // 주소체계(ex:AF_INET)

    );

    hostent구조체의 정의는 다음과 같다

    struct hostent {

    char* h_name; // official name of host

    char** h_aliases; // alias list

    short h_addrtype; // host address type

    short h_length; // length of address

    char** h_addr_list // list of addresses

    #define h_addr h_addr_list[0] // address, for backward compat

    };

    typedef struct hostent HOSTENT;

    • h_name

      공식 도메인 이름(offical domain name)이다

    • h_aliases

      한 호스트가 공식 도메인 이름 외에 다른 이름을 여러 개 가질 수 있으며, 이를 별명(alias name)이라 부른다

    • h_addrtype

      주소 체계를 나타내는 상수값이다. IPv4를 사용하는 경우 AF_INET값이 저장된다

    • h_length

      IP주소의 길이(바이트 단위)를 나타낸다. IPv4를 사용하는 경우 4(32비트)가 저장된다.

    • h_addr_list

      네트워크 바이트 정렬된 IP주소를 나타낸다. 한 호스트가 여러 IP주소를 가진 경우, 이 포인터를 따라가면 모든 IP주소를 얻을 수 있다. 특정 호스트에 접속할 때는 대개 첫번째 IP주소만 이용하므로 h_addr_list[0]을 접근하게 되는데, 매크로를 이요하여 재정의한 h_addr을 사용하면 편리하다

    gethostbyname( ), gethostbyeaddr( )함수 모두 성공하면 TRUE, 실패하면 FALSE를 리턴한다.

    • 사용 예

// 도메인 이름 -> IP 주소

BOOL getIPAddr(char* name, IN_ADDR *addr)

{

HOSTENT* ptr = gethostbyname(name);

if( ptr == NULL )

{

err_display("gethostbyname()");

return FALSE;

}

memcpy(addr, ptr->h_addr, ptr->h_length);

return TRUE;

}

// IP주소 -> 도메인 이름

BOOL getDomainName(IN_ADDR addr, char* name)

{

HOSTENT* ptr = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);

   

if(ptr == NULL)

{

err_display("gethostbyaddr()");

return FALSE;

}

strcpy(name, ptr->h_name);

return TRUE;

}

  • 예제 INameResolution

#include <winsock2.h>

#include <stdlib.h>

#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

   

void err_display(char* msg)

{

LPVOID lpMsgBuf;

FormatMessage(

FORMAT_MESSAGE_ALLOCATE_BUFFER |

FORMAT_MESSAGE_FROM_SYSTEM,

NULL, WSAGetLastError(),

MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),

(LPTSTR)&lpMsgBuf, 0, NULL);

printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf);

LocalFree(lpMsgBuf);

}

   

// 도메인 이름 -> IP주소

BOOL GetIPAddr(char* name, IN_ADDR* addr)

{

HOSTENT* ptr = gethostbyname(name);

if( ptr == NULL )

{

err_display("gethostbyname()");

return FALSE;

}

   

memcpy(addr, ptr->h_addr, ptr->h_length);

return TRUE;

}

   

// IP주소 -> 도메인 이름

BOOL GetDomainName(IN_ADDR addr, char* name)

{

HOSTENT* ptr = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);

if( ptr == NULL )

{

err_display("gethostbyaddr()");

return FALSE;

}

strcpy(name, ptr->h_name);

return TRUE;

}

   

int main(int argc, char* argv[])

{

WSADATA wsa;

if( WSAStartup(MAKEWORD(2,2),&wsa) != 0 )

{

return -1;

}

   

// 도메인 이름 -> ip주소

IN_ADDR addr;

if( GetIPAddr("kr.yahoo.com", &addr) )

{

// 성공이면 결과 출력

printf("IP주소=%s\n", inet_ntoa(addr));

   

// IP주소 -> 도메인 이름

char name[256];

if( GetDomainName(addr, name) )

{

// 성공이면 결과 출력

printf("도메인 이름 = %s\n", name);

}

}

   

WSACleanup();

   

return 0;

}

반응형

'책정리 > 윈도우 네트워크 프로그래밍' 카테고리의 다른 글

5장 멀티스레드  (0) 2015.04.20
4장 TCP 서버/클라이언트 구조  (0) 2015.04.13
2장 윈도우 소켓 시작하기  (0) 2015.04.10
1장 네트워크와 소켓 프로그래밍  (0) 2015.04.07
목차  (0) 2015.04.07