열렬히.뛰기

3. 주소체계와 데이터 정렬

네트워크 > 소켓 프로그래밍 > 소켓 프로그래밍 > 3. 주소체계와 데이터 정렬

이론 : IP주소와 포트번호

네트워크 이론과 겹치는 내용이므로 짧게 다루고 넘어감.

javascript
<IP주소 복습>
* IPv4 : 4바이트 주소체계
* IPv6 : 16바이트 주소계체
* 이 책에서는 IPv4를 위주로 설명하고 있다.

IP는 네트워크 주소와 호스트 주소로 나뉠 수 있다.

<포트번호>
* 네트워크를 사용하는 어플리케이션들을 구분하기 위한 장치
	ex. 동영상을 위한 포트와 인터넷을 위한 포트를 구분
* 하나의 운영체제 내에서 포트번호는 1개의 소켓에게만 부여.
 
* 0 ~ 1023번 : Well-known port
* 1024 ~ 65535번 : 비어있는 포트

<데이터가 들어오는 과정>
1. 전선
2. NIC (네트워크 인터페이스 카드) => IP주소 사용
3. 운영체제에 의해 포트로 분배 => 포트번호 사용

주소정보의 표현 : 구조체

IP주소와 포트번호를 소켓에 넣는 방법은?

주소정보 저장용 구조체

이를 위해 만들어진 구조체가 바로 sockaddr_in

c
struct sockaddr_in
{
	sa_family_t sin_family; 	// 주소체계 (signal family)
	uint16_t sin_port;			  // 16비트 포트번호 (signal port)
	struct in_addr sin_addr;	// 32비트 ip주소 (signal address)
	char sin_zero[8];			    // 패딩 (현재는 사용하지 않음)
};

sin_family : 주소체계를 의미.

  • 쓸 수 있는 대표적인 값은 다음과 같다.

    AF_INET = IPv4, AF_INET6 = IPv6, AF_LOCAL = 로컬 통신

sin_port : 포트번호를 저장. 단, 네트워크 바이트 순서대로 저장해야 한다.

sin_addr : ip주소를 저장. 단, 네트워크 바이트 순서대로 저장해야 한다.

  • 이를 이루는 구조체 in_addr 는 다음과 같다.
c
struct in_addr
{
		// 32비트 인터넷 주소.
		in_addr_t s_addr; 
}

sin_zero : 예전에는 sin_zero를 쓰지 않으면 메모리 정렬 문제 발생

  • 구조체의 크기를 특정한 배수로 맞춰야지만 해당 문제를 해결.
  • 해당 목표를 달성할 때까지 0을 채워 넣으면 된다.
  • 현재는 이러한 문제가 더 이상 없기 때문에 해결.

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

바이트 저장순서

CPU가 바이트를 저장하는 순서는 다음과 같다.

  1. 빅 엔디안 : 상위 바이트의 값을 작은 번지수로
  2. 리틀 엔디안 : 상위 바이트의 값을 큰 번지수로

이를 호스트 바이트 순서라고 한다. 인텔 CPU는 리틀 엔디안 방식을 따른다.

그런데, 소켓 프로그래밍을 할 때는 바이트 저장방식이 2가지인 것이 문제가 된다.

ip주소나 포트번호 역시 바이트인데, 통신하는 컴퓨터가 다른 호스트 바이트 순서를 가진다면?

그래서, 네트워크 바이트 순서라는 것을 만들었고, 이는 다음과 같다.

  • 네트워크 바이트 순서빅 엔디언 방식.

이를 위한 함수는 다음과 같다.

c
// h : 호스트(host) 바이트 순서
// n : 네트워크(network() 바이트 순서
// s : short
// l : long

unsigned short htons(unsigned short); // host 방식을 network 방식으로, short
unsigned short ntohs(unsigned short); // network 방식을 host 방식으로, short
unsigned long htonl(unsigned long);   // host 방식을 network 방식으로, long
unsigned long ntohl(unsigned long);   // network 방식을 host 방식으로, long

인터넷 주소 변환

문자열네트워크 바이트 순서의 정수로 변환하는 함수들을 알아보자.

c
#include <arpa/inet.h>

in_addr_t inet_addr(const char *string);
int inet_aton(const char *string, struct in_addr *addr);
  • 문자열을 ip주소 그 자체로 바꾸고 싶다면 inet_addr
  • ip주소와 포트번호가 같이 담긴 구조체를 변경 시 inet_aton
    • 리턴값은 0과 1로, 실패/성공을 의미한다.
  • 둘 다 문자열을 32비트 정수형태로 바꿔주고, 네트워크 바이트 순서를 맞춰주며, 잘못된 형태의 문자열을 검출할 수 있다.

반대로, 네트워크 바이트 순서의 정수문자열 정보로 바꾸는 함수도 있다.

c
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr adr);
  • ip주소를 문자열로 바꾸는 역할. 성공 시 변환된 문자열의 주소값, 실패 시 -1
  • 사용자가 동적 할당을 안 해도 됨. 따라서 반환값을 미리 복사하는 것이 필요.

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

정리 : 인터넷 주소 초기화

c
struct sockaddr_in serv_addr;

// addr의 모든 멤버를 0으로 초기화
memset(&serv_addr, 0, sizeof(serv_addr));

// sin_family = 주소체계 지정
serv_addr.sin_family = AF_INET;
// s_addr = 문자열 기반의 ip주소 초기화
serv_addr.sin_addr.s_addr = inet_addr("211.217.168.13");
// sin_port = 문자열 기반의 포트번호 초기화
serv_addr.sin_port = htons(atoi("9910")); 

INADDR_ANY

c
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

서버 소켓의 생성 과정에서 매번 서버의 IP주소를 입력하기 귀찮을수도 있다.

이때, INADDR_ANY라는 상수를 이용하면 서버의 IP주소를 자동으로 할당할 수 있다.

인터넷 주소 할당

c
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
  • sockfd : 소켓 = 파일 디스크립터
  • myaddr : 주소정보 구조체의 주소값
  • addrlen : myaddr의 길이