소켓 주소 구조체 다루기(윈도우 네트워크 프로그래밍)
TRANSCRIPT
소켓 주소 구조체 다루기
NHN NEXT 장문익
소켓 주소 구조체(socket address structures)
네트워크 프로그램에서 필요로 하는 주소 정보를 담고 있는 구조체
다양한 소켓 함수의 인자로 사용한다.
여러 소켓 주소 구조체 중 가장 기본이 되는 것은 SOCKADDR
socket address structure
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
typedef struct sockaddr SOCKADDR;
sa_family: 주소 체계를 나타내며, 부호 없는
16비트 정수값을 사용한다.
TCP/IP 프로토콜을 사용한다면 이 값은
AF_INET이 된다.(당연한 말이다. AF_INET는
TCP/IP를 사용하기 위한 주소체계니까)
sa_data[14]: 해당 주소 체계에서 사용하는
주소 정보를 담고 있다. 주소 체계에 따라 필요
한 정보가 다르다.
가장 일반적인 형태는 바이트 배열
TCP/IP 프로토콜을 사용하면 여기에는 IP 주소
와 포트 번호가 저장된다.
TCP/IP 프로토콜을 위한 SOCKADDR_IN
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
typedef struct sockaddr_in SOCKADDR_IN;
sin_family: 주소 체계. 부호 있는 16비트 정수
값 사용. 항상 AF_INET값 사용.
sin_port: 포트 번호. 부호없는 16비트 정수값.
sin_addr: IP 주소. 32비트 in_addr 구조체.
sin_zero: 실제로 사용하지는 않음. 항상 0으로
채움
IP 주소를 저장하는 in_addr 구조체
struct in_addr {
union {
struct { u_char s_b1, s_b2, s_b3, s_b4; } S_un_b;
struct { u_short s_w1, s_w2; } S_un_b;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr
};
typedef struct in_addr IN_ADDR;
소켓 주소 구조체 사용
소켓 주소 구조체를 초기화한 후 소켓 함수에 넘겨주는 경우( f()는 소켓 함수)
SOCKADDR_IN addr1;
// 소켓 주소 구조체 초기화
...
f(( SOCKADDR* ) &addr1, ...); // 소켓 함수
// 소켓 주소 구조체의 크기가 크기 때문에
// 소켓 함수 인자로 주소값을 넘긴다.
소켓 주소 구조체 사용
소켓 함수가 소켓 주소 구조체를 입력으로 받아서 초기화하고,
애플리케이션이 이를 출력 등의 목적으로 사용하는 경우( g()는 소켓함수)
SOCKADDR_IN addr2;
f(( SOCKADDR* ) &addr2, ...);
// 소켓 주소 구조체를 사용한다.
...
바이트 정렬 함수
byte ordering
메모리에 데이터를 저장할 때의 바이트 순서를 나타내는 용어
big-endian: MSB(Most Significant Byte)부터 차례로 저장되는 방식
little-endian: LSB(Least Significant Byte)부터 차례로 저장되는 방식
big-endian, little-endian
byte ordering이 약속되어야 하는 이유1
IP 주소, 포트 번호 등과 같이 프로토콜 구현을 위해 필요한 정보
종단 시스템이 보낸 패킷의 IP 헤더에는 IP 주소가 포함되어 있고, 이 패킷을 라우터가 받으면, IP 주
소를 참조하여 다음 위치의 라우터에 보낸다.
byte-ordering을 약속하지 않으면 IP 주소 해석이 달라져서 잘못된 라우팅이 될 수 있다.
byte ordering이 약속되어야 하는 이유2
포트 번호의 바이트 정렬 방식
두 종단 시스템이 포트 번호의 바이트 정렬 방식을 약속하지 않을 경우, 잘못된 목적지 프로세스에
데이터가 전달될 수 있다.
network byte ordering
IP 주소와 포트 번호의 바이트 정렬방식이 약속되지 않으면 앞의 문제가 발생한다.
그래서 IP 주소와 포트 번호의 바이트 정렬방식은 big-endian으로 통일하여 사용한다.
네트워크 용어로 big-endian을 network byte ordering이라 한다.
시스템이 사용하는 고유한 byte ordering을 host byte ordering이라 부른다.
byte ordering이 약속되어야 하는 이유3
애플리케이션이 주고 받는 데이터
두 종단 시스템이 교환하는 데이터에 대해 byte ordering이 약속되지 않으면, 데이터 해석의 문제
가 발생할 수 있다.
대게 network byte ordering을 사용한다.
hton*()
윈속에서 제공하는 byte ordering 유틸리티 함수
host byte ordering로 저장된 값을 입력으로 받아서 network byte ordering으로 변환한 값을 리턴
한다.
함수 이름은 ‘H’ost ‘T’o ‘N’etwork u_’S’hort 방식으로 지었다고 추측한다.
u_short htons( u_short hostshort ); // host-to-network-short
u_long htons( u_long hostong ); // host-to-network-long
ntoh*()
network byte ordering으로 저장된 값을 입력으로 받아서 host byte ordering으로 변환한 값을 리
턴한다.
u_short ntohs( u_short networkshort ); // network-to-host-short
u_long ntohs( u_long networkshort ); // network-to-host-long
hton*()함수와 ntoh*()함수 호출
winsock 2.x에서 제공하는 byte ordering 함수
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 WSAHtohs ( SOCKET s, u_long netlong, u_long* lphostlong );
첫 번째 인자는 소켓 디스크럽터
변환된 결과는 함수 리턴값이 아닌 세 번째 인자로 전달한다.
SOCKADDR_IN 구조체의 byte ordering
sin_family는 host byte ordering을 따른다.
sin_port와 sin_addr은 network byte ordering을 따른다.
hton*(), ntoh*() 사용 시 주의할 점
u_short x = 0x1234;
// 잘못된 사용
printf("wrong!\n");
printf("0x%x -> 0x%x\n", x, htonl(x));
htol() 함수는 u_long을 리턴한다.
u_short를 전달하여 u_long을 리턴하므로, 남
는 4byte는 0으로 채워진다.
결과
wrong!
0x1234 -> 0x34120000
IP 주소 변환 함수
윈도우 애플리케이션에서는 IP 주소를 입력 받기 위해 cmd와 edit contro을 이용한다고 한다.
이때 애플리케이션은 IP 주소를 문자열 형태로 전달받게 되므로 이를 32비트 숫자로 변환해야 한다.
inet_addr() 함수는 문자열 형태로 IP 주소를 입력받아 32비트 숫자(network byte ordering)로 리
턴한다.
inet_ntoa() 함수는 32비트 숫자(network byte ordering)로 IP 주소를 입력받아 문자열 형태로 리
턴한다.
inet_addr()와 inet_ntoa()
unsigned long inet_addr( const char *cp );
char* inet_ntoa( struct in_addr in ); // network-to-ascii
바이트 정렬 함수와 IP 주소 변환 함수 예시1
// 소켓 주소 구조체 초기화
SOCKADDR_IN addr;
ZeroMemory( &addr, sizeof( addr ) );
addr.sin_family = AF_INET;
addr.sin_addr.s_addr =inet_addr( "147.46.114.70" );
addr.sin_port = htons( 9010 );
// 소켓 함수 호출
f( ( SOCKADDR * )&addr, ... );
inet_addr() 함수는 이미 network byte ordering된 IP 주소를 리턴하므로 htonl() 함수를 적용하면 안 된다.
// wrong!
addr.sin_addr.s_addr =htonl(inet_addr( "147.46.114.70" ));
바이트 정렬 함수와 IP 주소 변환 함수 예시1
// 소켓 함수 호출
g( ( SOCKADDR *)&addr, ... );
// IP 주소와 포트 번호 출력
printf("IP 주소 = %s, 포트 번호 = %d",
inet_ntoa( addr.sin_addr ), ntohs( addr.si
n_port ) );
addr.sin_addr은 이미 network byte orderin
된 IP 주소이므로 ntohl() 함수를 적용하면 안
된다.
// error
printf("IP 주소 = %s, 포트 번호 = %d",
inet_ntoa( htonl( addr.sin_addr ) ),
ntohs( addr.sin_port ) );
심지어 자료형도 맞지 않다.
도메인 이름 시스템과 이름 변환 함수
domain name
IP 주소와 대응하는 이름으로, 사람이 기억해서 사용하기 쉽게 만든 것
TCP/IP 프로토콜은 도메인 이름을 인식하지 못하므로 사용자가 입력한 도메인 이름을 반드시 IP 주
소로 변환해야 한다.
IP 주소와 도메인 이름 사이의 변환 정보는 인터넷에 연결된 여러 개의 도메인 이름 서버(DNS
Server, Domain Name System Server)가 관리한다.
도메인 이름-> IP주소(network byte ordering)
// domain name -> IP address(network byte ordering)
struct hostent* gethostbyname(
const char *name // domain name
);
IP 주소(network byte ordering) -> 도메인 이름
// IP address(network byte ordering) -> domain name
struct hostent* gethostbyaddr(
const char *addr, // IP address(network byte ordering)
int len, // IP address 길이
int type // 주소 체계(예 : TCP/IP의 경우는 AF_INET)
);
hostent 구조체
typedef 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] // addrss, for backward compatibilty
} ;
typedef struct hostent HOSTENT;
hostent 구조체
h_name: 공식 도메인 이름
h_aliases: 한 호스트가 공식 도메인 이름 외에 다른 이름을 여러 개 가질 수 있으며, 이를 별명(alias
name)이라 부른다.
h_addrtype: 주소체계를 나타내는 상수값. IPv4를 사용하는 경우 AF_INET.
h_length: IP 주소의 길이(바이트 단위). IPv4를 사용하는 경우 4(32비트)가 저장된다.
h_addr_list: network byte ordering 된 IP 주소를 나타낸다. 한 호스트가 여러 IP 주소를 가진 경우,
이 포인터를 따라가면 모든 IP 주소를 얻을 수 있다. 특정 호스트에 접속할 때는 대개 첫 번째 IP 주소
만 이용하므로 h_addr_list[0]를 접근하게 되는데, 매크로를 이용하여 재정의한 h_addr을 사용하면
편리하다.
hostent 구조체
gethostbyname() 함수를 이용한 유틸리티 함수
// domain name -> IP address
BOOL GetIPAddr( char* name, IN_ADDR* addr )
{
HOSTENT* ptr = gethostbyname(name);
if ( ptr == NULL ) {
err_display( "gethostbyname()" ); // error 출력 함수
return FALSE;
}
memcpy( addr, ptr->h_addr, ptr->h_length );
return TRUE;
}
gethostbyaddr() 함수를 이용한 유틸리티 함수
// IP address -> domain name
BOOL GetDomainName( IN_ADDR* addr, char* name)
{
HOSTENT* ptr = gethostbyaddr( (char *)&addr, sizeof(addr), AF_INET );
if ( ptr == NULL ) {
err_display( "gethostbyaddr()" );// error 출력 함수
return FALSE;
}
strcpy( name, ptr->h_name );
return TRUE;
}
참고자료
김선우. 윈도우 네트워크 프로그래밍. 한빛미디어