상세 컨텐츠

본문 제목

네트워크 프로그래밍 - 소켓 IP 주소와 PORT 번호, byte order(바이트 순서), byte order convert (바이트 순서 변환)

개발/Network

by 2021. 9. 26. 22:49

본문

반응형

IP 주소

컴퓨터를 구분하데 사용하는 주소 중 하나이다. 4byte 체계인 IPv4와 16byte 체계인 IPv6이 있다. IP는 소켓을 생성할 때 기본적인 프로토콜을 지정해야하며, 네트워크 주소와 호스트 주소로 나뉜다. 네트워크 주소로 네트워크를 찾은 후 호스트 주소를 이용해서 그 안의 호스트를 구분한다.

PORT번호

어플리케이션에 부여되는 번호이다. port번호는 16비트로 표현하고, 0~65535까지이다.

0~1023은 well-known port라 하여 용도가 결정되어있는 port이다.

운영체제에서 port번호를 확인한 후 해당 소켓으로 보내준다.

sockaddr_in 구조체 (IPv4)

소켓에 IP주소와 port번호를 할당하는데 bind함수를 이용하는데, bind를 통해서 소켓에 IP와 port번호를 할당하기 위해서 IPv4 프로토콜에서는 sockaddr_in 구조체를 이용한다.

sockaddr_in 구조체에 IP주소와 port번호를 담아 bind 함수의 매개변수로 보내면, 해당 IP주소와 port번호가 소켓에 할당된다.

struct sockaddr_in
{
    sa_family_t		sin_family; // 주소체계
    unit16_t		sin_port; // PORT 번호
    struct in_addr	sin_addr; // 32비트 IP주소
    char		sin_zero[8]; // 사용되지 않음
};

sockaddr_in 구조체의 형태는 위와 같다.

sin_family

주소체계 정보를 저장한다. 

주소체계는 위 세 가지의 값 중 하나의 값을 가진다. 

sin_port

서버가 사용할 16비트 port번호를 저장한다. 

sin_addr

32비트 IP주소 정보를 저장한다. sin_addr에는 in_addr 구조체가 사용된다.

struct in_addr
{
	in_addr_t	s_addr;
};

in_addr_t 자료형은 IP주소정보를 담고있는, uint32_t(unsigned 32-bit int)로 정의된 자료형이다.

32bit는 4byte 이고 165.246.32.45 와 같은 형태가 32bit로 표현하는 형태이다.

이 형태가 어떻게 나왔냐면, 본디 32bit는 40억 가량의 십진수이다. 이 십진수를 이진법으로 바꾼 뒤 4자리씩 즉 1byte씩 끊어서 표현한 것이 통상적으로 보는 IP의 형태 255.255.255.255이다.

sin_zero

0으로 채워야하는 값인데, 그 의미는 나중에 알아보자

 

sockaddr_in이 클라이언트와 서버단에서 어떻게 이용되는지를 알아보자.

 

클라이언트에서는 상대방(서버)의 IP주소와 port번호를 sockaddr_in에 적은 후 connect 함수의 매개변수로 보내서 실행하면 상대방 서버에 접속한다.

 

서버에서는 sockaddr_in 구조체에 자신의 IP주소와 port번호를 입력하고, 다음과 같이 bind의 매개변수로 보낸다.

struct sockaddr_in serv_addr;
....
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
	error_handling("bind() error");
....

위 코드를 보면 sockaddr_in 변수를 sockaddr 형으로 형변환을 하는데, 본디 bind함수의 매개변수인 sockaddr은 다양한 주소체계의 주소정보를 담을 수 있도록 정의되어있는 자료형이다.

struct sockaddr
{
	sa_family_t 	sin_family; // 주소체계(Address Family)
	char		sa_data[14]; // 주소정보
};

위 자료형을 보면 IPv4의 주소정보인 port번호, IP주소 등을 sa_data 하나에 넣기가 불편하기 때문에 IPv4의 주소정보를 쉽게 담을 수 있도록 sockaddr_in 자료형을 별도로 만들었고, bind 함수에서는 sockaddr형으로 형 변환을 하여 전송하는 것이다. 

 

네트워크 바이트

cpu별로 정수를 저장하는 방식이 다르다.

Big Endian (빅 엔디안)

상위 바이트의 값을 작은 번지수에 저장하는 방식이다.

위 그림과 같이 상위 바이트의 값 0x12를 작은 번지수인 0x20번지부터 저장한다.

Little Endian (리틀 엔디안)

상위 바이트의 값을 큰 번지수에 저장하는 방식이다. 

위 그림과 같이 상위 바이트의 값 0x12를 작은 번지수인 0x24번지부터 저장한다.

 

cpu별로 저장방식이 다른데 cpu별 저장 방식을 host byte order이라고 하고,

네트워크에서는 network byte order이라고 한다. network byte order에서는 정수값을 주고 받을 경우 Big Endian 방식으로 통일되었다. 

 

Byte Order 변환

컴퓨터에서 사용하는 host byte order는 다를 수 있기 때문에 네트워크로 보낼 때 network byte order로 변환한 후 보내고, 상대방이 받았을 경우에도 network byte order에서 host byte order로 변환 후 사용한다.

 

byte 변환 함수는 host에서 network로 변경할 때 사용하는 hton, network에서 host로 변환할 때 사용하는 ntoh가 있고, 자료형에 따라 다음과 같이 네 가지로 분류된다.

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

 다음의 예시 코드를 보자.

int main(int argc, char *argv[])
{
    unsigned short host_port=0x1234;
    unsigned short net_port;
    unsigned long host_addr=0x12345678;
    unsigned long net_addr;
    
    net_port=htons(host_port);
    net_addr=htonl(host_addr);
    
    printf("Host ordered port: %#x \n", host_port);
    printf("Network ordered port: %#x \n", net_port);
    printf("Host ordered address: %#lx \n", host_addr);
    printf("Network ordered address: %#lx \n", net_addr);
    
    return 0;
}

실행 결과는 다음과 같다.

root@my_linux:/tcpip# gcc endian_conv.c -o conv
root@my_linux:/tcpip# ./conv
Host ordered port: @x1234
Network ordered port: 0x3412
Host ordered address: 0x12345678
Network ordered address: @x78563412

host byte order와 network byte order에 따라서 순서가 다르게 변환되는 것을 확인할 수 있다.

문자열 정보를 network byte order 정수로 변환

#include <arpa/inet.h>

in_addr_t inet_addr(const char * string); 
// 성공 시 빅 엔디안으로 변환된 32비트 정수 값, 실패 시 INADDR_NONE 반환

"211.214.107.99"와 같은 문자열을 IP주소 정보 32비트 정수형으로 반환할 수 있는 함수이다.

int main(int argc, char *argv[])
{
    char *addr1="1.2.3.4";
    char *addr2="1.2.3.256";
    
    unsigned long conv_addr=inet_addr(addr1);
    if (conv_addr==INADOR_NONE)
    	printf("Error occured! \n");
    else
    	printf("Network ordered integer addr: %#lx \n", conv_addr);
        
    conv_addr=inet_addr(addr2);
    if (conv_addr==INADDR_NONE)
    	printf("Error occureded \n");
    else
    	printf("Network ordered integer addr: %#lx \n\n", conv_addr);
    return 0;
}

실행 결과는 다음과 같다

root@my_linux:/tcpip# gcc inet_addr.c -o addr
root@my_linux:/tcpip# ./addr
Network ordered integer addr: @x4030201
Error occureded

위 코드를 보면 inet_addr함수를 통해 addr1에 담긴 IP주소 문자열이 network byte order인 Big Endian 형태로 저장된 것을 확인할 수 있다.

add2에 저장된 값의 경우 256이 들어가있는데 1byte를 십진수로 표현한 최대 값은 255이기 때문에 변형된 결과에서 Error가 뜨는 것을 확인할 수 있다.

inet_aton

#include <arpa/inet.h>

int inet_aton(const char * string, struct in_addr * addr);
// 성공시 1(true), 실패 시 0(false) 반환
// string : 변환할 IP주소 정보를 담고 있는 문자열의 주소 값 전달
// addr : 변환된 정보를 저장할 in_addr 구조체 변수의주소 값 전달

inet_addr 함수와 같이 IP주소의 문자열 형태를 32bit 정수형 주소의 Big Endian 형태로 변환해주는 함수이다.

int main(int arge, char *argv[])
{
    char *addr="127.232.124.79";
    struct sockaddr_in addr_inet;
    
    if(!inet_aton(addr, &addr_inet.sin_addr))
    	error_handling("Conversion error");
    else
        printf("Network ordered integer addr: %#x \n",
            addr_inet.sin_addr.s_addr);
            
    return 0;
}
root@ny_linux:/tcpip# gcc inet_ntoa.c -o ntoa
root@my_linux:/tcpip# ./ntoa

Dotted-Decimal notation1: 1.2.3.4
Dotted-Decimal notation2: 1.1.1.1
Dotted-Decimal notation3: 1.2.3.4

inet_ntoa

#include <arpa/inet.h>

char * inet_ntoa(struct in_addr adr); // 성공 시 변환된 문자열의 주소 값, 실패 시 -1 반환

반대로 network byte order로 정렬된 32비트 정수형 IP주소를 string형태로 변환하는 함수이다.

struct sockaddr_in addr1, addr2;
char *str_ptr;
char str_arr[20];

addr1.sin_addr.s_addr=hton1(6x1620304) ;
addr2.sin_addr.s_addr=hton1(0x1018101) ;

str_ptr=inet_ntoa(addr1.sin_addr);
strcpy(str_arr, str_ptr);
printf("Dotted-Decimal notation1: %s \n", str_ptr);

inet_ntoa(addr2.sin_addr);
printf("Dotted-Decimal notation2: Xs \n", str_ptr);
printf("Dotted-Decimal notation3: %s \n", str_arr);

return 0;

inet_ntoa 함수를 이용하여 addr1.sin_addr의 값을 문자열 형태로 변환한다. 

root@ny_linux:/tcpip# gcc inet_ntoa.c -o ntoa
root@my_linux:/tcpip# ./ntoa

Dotted-Decimal notation1: 1.2.3.4
Dotted-Decimal notation2: 1.1.1.1
Dotted-Decimal notation3: 1.2.3.4

 

반응형

관련글 더보기