[Socket] 주소체계와 데이터 정렬

소켓에 할당되는 IP주소와 PORT번호

인터넷 주소(Internet Address)

인터넷 상에서 컴퓨터를 구분하는 목적으로 사용되는 주소를 말한다. 4바이트 주소체계인 IPv416바이트 주소체계인 IPv6가 존재한다. 소켓을 생성할 때 기본적인 프로토콜을 지정해야 한다.

인터넷 주소는 네트워크 주소 호스트 주소로 나뉘게 되는데, 네트워크 주소를 이용해 네트워크를 찾고, 호스트 주소를 이용해 호스트를 구분한다.

 

PORT 번호

IP는 컴퓨터에 부여하는 값으로 컴퓨터를 구분하는 용도로 사용되고, PORT번호는 소켓에 부여하는 값으로 소켓을 구분하는 용도로 사용된다.

하나의 프로그램 내에서는 둘 이상의 소켓이 존재할 수 있기 때문에, 둘 이상의 PORT가 하나의 프로그램에 의해 할당될 수 있다.

0~1023은 Well-known PORT로 Server가 사용하며, 이미 용도가 결정되어 있다.

Client는 큰 Port번호를 사용한다. → Random Number


주소 정보의 표현

IPv4 기반의 주소 표현을 구한 구조체

IP주소와 PORT번호는 구조체 sockaddr_in의 변수에 담아서 표현한다.

struct sockaddr_in{
	sa_family_t     sin_family; //주소 체계
	uint16_t        sin_port;   //PORT 번호
	struct in_addr  sin_addr;   //32비트 IP 주소
	char            sin_zero[8];//사용되지 않음
};
  • sin_family : 주소체계 정보를 저장
    • AF_INET : IPv4
    • AF_INET6 : IPv6
    • AF_LOCAL : 로컬 통신을 위한 unix protocol
  • sin_port : 16비트 PORT번호 저장
  • sin_addr : 32비트 IP주소정보 저장
  • sin_zero : 특별한 의미X, 반드시 0으로 채워야 한다. 구조체 sockaddr_in의 크기를 구조체 sockaddr와 일치시키기 위해 필요하다.
struct sockaddr{
	sa_family_t sin_family;  //주소체계(Address family)
	char        sa_data[14]; //주소 정보
};
구조체 sockaddr은 다양한 주소체계의 주소 정보를 담을 수 있도록 정의되었다. 이로 인해, IPv4의 주소 정보를 담기가 불편하다. 그래서 동일한 바이트 열을 구성하는 구조체 sockaddr_in이 정의되었으며, 이를 이용해 쉽게 IPv4의 주소 정보를 담을 수 있다.
struct sockaddr_in serv_addr;
...
if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
	perror("bind() error");
...

 

구조체 변수 sockaddr_in은 bind함수의 인자로 전달되는데, 매개변수 형이 sockaddr이기 때문에, 형 변환(casting)이 필요하다.


네트워크 바이트 순서와 인터넷 주소 변환

CPU에 따라 상위 바이트를 하위 메모리 주소에 저장하기도 하고, 상위 바이트를 상위 메모리 주소에 저장하기도 한다.

→ CPU마다 데이터를 표현 및 해석하는 방식이 다르다.

 

Big Endian(빅 엔디안) → 상위 바이트의 값을 작은 번지수에 저장

정수 0x12345678

Little Endian(리틀 엔디안) → 상위 바이트의 값을 큰 번지수에 저장

정수 0x12345678

 

호스트 바이트 순서 : CPU별 데이터 저장방식을 의미한다.

네트워크 바이트 순서 : 통일된 데이터 송수신 기준을 의미한다. Big Endian 기준이다.

 

바이트 순서의 변환

unsigned short htons(unsigned short); //short = 2 byte
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);   //long = 4 byte
unsigned long ntohl(unsigned long);

htons

  • h = 호스트 바이트 순서
  • n = 네트워크 바이트 순서
  • s = 자료형 short
  • l = 자료형 long

인터넷 주소의 초기화와 할당

문자열 정보를 네트워크 바이트 순서의 정수로 변환

#include <arpa/inet.h>
in_addr_t inet_addr(const char* string);
//성공 시 big endian으로 변환된 32비트 정수 값, 실패 시 INADDR_NONE return

 

“211.214.107.99”와 같이 점이 찍힌 10진수로 표현된 문자열을 전달하면, 해당 문자열 정보를 참조해 IP주소정보를 32비트 정수형으로 반환한다.

 

1byte는 0~255의 범위를 가지고 있다. 따라서 범위를 벗어나는(e.g. “.... 256”) 문자열이 오면 Error발생

e.g.
char *addr1 = "1.2.3.4";
unsigned long conv_addr = inet_addr(addr1);
//0x4030201(네트워크 바이트 순서로 정렬)과 같이 32비트의 정수 형태로 IP주소를 변환한다.

 

inet_aton

ASCII를 숫자로 변환한다.

#include <arpa/inet.h>
int inet_aton(const char* string, struct in_addr* addr);
//성공 시 1, 실패 시 0 return

 

  • string : 변환할 IP주소 정보를 담고 있는 문자열의 주소 값
  • addr : 변환된 정보를 저장할 in_addr 구조체 변수의 주소 값

inet_ntoa

숫자에서 ASCII로 변환한다.

#include <arpa/inet.h>
char* inet_ntoa(struct in_addr adr);
//성공 시 변환된 문자열의 주소 값, 실패 시 -1 return

 

인터넷 주소의 초기화

일반적인 인터넷 주소의 초기화 과정은 다음과 같다.

struct sockaddr_in addr;
char *serv_ip = "211.217.168.13";
char *serv_port = "8080";

memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(serv_ip); //문자열 기반의 IP주소 초기화
addr.sin_port = htons(atoi(serv_port));    //문자열 기반의 PORT번호 초기화

 

INADDR_ANY

현재 실행 중인 컴퓨터의 IP를 소켓에 부여할 때 사용된다. 주로 서버 프로그램의 구현에 사용된다.

...
addr.sin_addr.s_addr = htonl(INADDR_ANY);//내가 쓰는 IP 중 아무거나 갖다 써라!
...

→ 이 경우, 소켓의 주소는 INADDR_ANY가 지정되어 소켓의 PORT번호만 인자를 통해 전달하면 된다.