Part1_주소 체계와 데이터 정렬(2)

·2023년 10월 31일
0

[인터넷 주소 조작하기]

sockaddr_in 안에서 주소를 나타내기 위해 선언되어 있는 멤버의 데이터 타입은 unsigned long

따라서 IP 주소 정보를 할당하기 위해 unsigned long 타입으로 IP 주소를 표현할 수 있어야 함

(1) inet_addr() : Dotted-Decimal Notation을 Big-Endian 32비트 값으로 변환

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa.inet.h>

unsigned long inet_addr(const char* string);
// 성공 시 Big-Endian 32비트 값, 오류 발생시 INADDR_NONE 리턴

[Dotted-Decimal Notation → unsigned long 타입의 데이터로 변경]

// inet_addr.c

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char** argv)
{
	char* addr1 = "1.2.3.4";
	char* addr2 = "1.2.3.256";
	unsigned long conv_addr;

	conv_addr = inet_addr(addr1);
	if(conv_addr == INADDR_NONE)
		printf("Error Occur : %d \n", conv_addr);
	else
		printf("Unsigned long addr(network ordered) : %x \n", conv_addr);

	conv_addr = inet_addr(addr2);
	if(conv_addr == INADDR_NONE)
		printf("Error Occured : %d \n\n", conv_addr);
	else
		printf("Unsigned long addr(network ordered) : %x \n\n", conv_addr);

	return 0;
}

[실행 결과]

(2) inet_aton() : inet_addr 함수 보다 개선된 데이터 변환 함수

어떤 의미에서 개선됐을까?

inet_addr() 함수는 unsigned long 타입의 값이 리턴되기 때문에 리턴된 값을 구조체 sockaddr_in 안에 선언되어 있는 in_addr 구조체 안에 대입해야 함

그러나 inet_aton 함수는 대입하는 과정을 따로 거치지 않아도 됨

(인자로 in_addr 구조체의 포인터를 전달하기 때문에 자동적으로 변환된 값이 대입됨)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa.inet.h>

int inet_aton(const char* string, struct in_addr* addr);

// 성공 시 0이 아닌 값(true), 실패 시 0(false)가 리턴됨

[inet_aton 함수를 사용한 데이터 변환]

// inet_aton.c

#include <stdio.h>
#include <arpa/inet.h>

void error_handling(char* message);

int main(int argc, char** argv)
{
	char* addr = "1.2.3.4";
	struct sockaddr_in addr_inet;

	if(!inet_aton(addr, &addr_inet.sin_addr))
		error_handling("Conversion Error");
	printf("Unsigned long addr(network ordered) : %x \n\n", addr_inet.sin_addr.s_addr);

	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

[결과]

(3) int_ntoa() : 네트워크 바이트 순서의 32비트 값을 Dotted-Decimal Notation으로 변환

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char* inet_ntoa(struct in_addr addr);

// 성공 시 변환된 해당 문자열의 포인터를 실패 시 -1을 리턴

리턴 타입이 문자열 포인터임 → 문자열을 저장한 장소가 어딘가에 존재함을 의미

어디에 존재할까? → 함수 내부에 선언되어 있는 static 버퍼임

⇒ 즉, 다시 한 번 다른 주소 정보를 가지고 inet_ntoa 함수를 호출하게 되면 이 버퍼는 다른 데이터로 채워지게 됨(inet_ntoa()가 다시 호출되기 전까지만 리턴된 포인터가 유효함)

// inet_ntoa.c

#include <stdio.h>
#include <arpa/inet.h>

int main(void)
{
	struct sockaddr_in addr1, addr2;
	char* str;

	addr1.sin_addr.s_addr = htonl(0x1020304);
	addr2.sin_addr.s_addr = htonl(0x1010101);

	str = inet_ntoa(addr1.sin_addr);
	printf("Dotted-Decimal notation : %s \n", str);

	inet_ntoa(addr2.sin_addr); // inet_ntoa의 리턴 값을 str에 넣어주지 않았으나
	printf("Dotted-Decimal notation : %s \n\n", str); // 출력 결과를 보면 다른 결과가 나옴
	return 0;
}

[결과]

[인터넷 주소 초기화]

// 인터넷 주소 정보를 나타내는 구조체 변수 생성
struct sockaddr_in addr;

// IP 주소와 Port 선언
char* serv_ip = "211.21.168.13";
char* serv_port = "9190";

// memset 함수를 호출해서 인자로 전달된 구조체 변수를 0으로 초기화 (쓰레기 값 제거)
memset(&addr, 0, sizeof(addr_len));

// 프로토콜 체계 설정
addr.sin_family = AF_INET;

// 네트워크 바이트 순서로 된 32비트 IP 값으로 변환해서 대입
addr.sin_addr.s_addr = inet_addr(serv_ip);

// 정수 값으로 변환 후 htons()를 통해 네트워크 바이트 순서로 변환
addr.sin_port = htons(atoi(serv_port));

위의 코드는 IP와 Port 정보를 코드에 직접 넣어주고 있지만 좋은 방법은 아님

→ 컴퓨터에서 실행할 때마다 코드를 변경해주고 나서 다시 컴파일해야 하기 때문

아래와 같은 형식으로 주소 정보 구조체를 초기화 할 수도 있음

struct sockaddr_in addr;
char* serv_port = "9190";
memset(&addr, 0, sizeof(addr_len));
addr.sin_family = AF_INET;

// INADDR_ANY를 사용하면 현재 시스템의 IP 주소를 자동적으로 찾아서 할당해줌
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));

그럼 어떻게 해야 할까?

main 함수에 인자 값을 전달하는 방법이 더 나

[주소 정보 할당하기]

#include <sys/type.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr* myaddr, int addrlen);

// 성공 시 0, 실패 시 -1을 리턴 (성공 시 sockfd가 가리키는 소켓에 myaddr이 가리키는 주소 정보가 할당됨)
// sockfd : 주소를 할당하고자 하는 소켓의 파일 디스크립터를 인자로 전달
// myaddr : 할당하고자 하는 주소 정보를 지니고 있는 sockaddr_in 구조체 변수의 포인터를 인자로 전달
// 함수 선언에서 보면 sockaddr 구조체 변수의 포인터를 전달하라고 되어 있는데 이후에 설명 예정
// addrlen : 인자로 전달된 주소 정보 구조체의 길이를 전달

[소켓을 할당하고 주소를 할당하기 까지의 과정]

// bind_sock.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char* message);

int main(void)
{
	int serv_sock;
	char* serv_ip = "127.0.0.1";
	char* serv_port = "9190";
	struct sockaddr_in serv_addr;

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock == -1)
		error_handling("socket() error");

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(serv_ip);
	serv_addr.sin_port = htons(atoi(serv_port));

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

	printf("파일 디스크립터 %d의 소켓에 주소 할당까지 완료! \n\n", serv_sock);
	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

[실행 결과]

bind 함수의 두 번째 인자 타입이 sockaddr*로 선언되어 있음 → 그래서 형 변환해서 인자를 전달함

그럼 sockaddr 구조체는 어떻게 생겼을까?

struct sockaddr
{
	sa_family_t sin_family;
	char        sa_data[14];
};

struct sockaddr_in
{
	sa_family_t    sin_family;   // 주소 체계
	uint16_t       sin_port;     // 16비트 TCP / UDP Port
	struct in_addr sin_addr;     // 32비트 IPv4 주소
	char           size_zero[8]; // 사용되지 않음
};

struct in_addr
{
	uint32_t       s_addr;       // 32비트 IPv4 인터넷 주소
};

bind 함수의 두 번째 인자가 sockaddr인 이유는 sockaddr_in 구조체의 포인터와 sockaddr_un 구조체의 포인터 주소를 모두 인자로 받을 수 있어야 하기 때문에 범용적으로 사용 가능한 sockaddr을 씀

→ 왜 void*를 사용하지 않고 sockaddr*을 썼을까?

4.2BSD가 1983년에 발표 되었고, ANSI 표준 C가 1989년에 첫 번째 표준을 완성시킴
즉 이 때는 void*가 없었음..

0개의 댓글