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 리턴
// 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.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*가 없었음..