문자열 정보를 네트워크 바이트 순서의 정수로 변환하기
sockaddr_in 안에서 주소정보를 저장하기 위해 선언된 멤버는 32비트 정수형으로 정의되어 있습니다. 따라서 우리의 IP주소 정보의 할당을 위해 32비트 정수형태로 IP주소를 표현할 수 있어야 합니다. 그러나 문자열 정보에 익숙한 우리들에게 이는 만만치 않은 일입니다. IP주소 201.211.214.36을 4바이트 정수로 표현했을 때 얼마가 되는지 계산하는 걸 생각해보면 벌써부터 머리가 지끈지끈한 걸 느낄 수 있을 것입니다.
다행히 문자열로 표현 된 IP주소를 32비트 정수형으로 변환해 주는 함수가 있습니다. 뿐만 아니라, 이 함수는 변환과정에서 네트워크 바이트 순서로의 변환도 동시에 진행합니다.
// 성공 시 빅 엔디안으로 변환된 32비트 정수 값, 실패 시 INADDR_NONE 반환
#include <arpa/inet.h>
in_addr_t inet_addr(const char* string);
이 함수를 이용한 예제 코드를 한 번 보겠습니다.
#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"; // 1바이트당 표현할 수 있는 최대 정수의 크기가 255이므로 잘못된 주소
unsigned long conv_addr = inet_addr(addr1);
if (conv_addr == INADDR_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 occured\n");
else
printf("Network ordered integer addr: %#lx\n", conv_addr);
return 0;
}
결과는 아래와 같습니다.
위 결과에서 볼 수 있듯에 inet_addr 함수는 유효하지 못한 IP 주소에 대한 오류검출 능력도 갖고 있습니다. 이어서 볼 inet_aton 함수도 기능상으로는 inet_addr 함수와 동일합니다. 즉, 문자열 형태의 IP 주소를 32비트 정수, 그것도 네트워크 바이트 순서로 정렬해 반환합니다. 다만 구조체 변수 in_addr를 이용하는 점에서 차이를 보입니다. 활용도는 inet_aton 함수가 더 높습니다. 이 함수는 다음과 같은 인자들을 전달해줘야 합니다.
- const char* string: 변환할 IP 주소 정보를 담고 있는 문자열의 주소 값 전달
- struct in_addr* addr: 변환할 정보를 저장할 in_addr 구조체 변수의 주소 값 전달
// 성공 시 1, 실패 시 0 반환
#include <arpa/inet.h>
int inet_aton(const char* string, struct in_addr* addr);
다음 예제 코드를 통해서 inet_aton 함수의 사용방법을 확인해보겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
void error_handling(char* message);
int main(int argc, 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);
return 0;
}
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
결과는 아래와 같습니다.
마지막으로 inet_aton 함수의 반대기능을 제공하는 함수를 소개하겠습니다. 이 함수는 네트워크 바이트 순서로 정렬된 정수형 IP주소 정보를 우리가 눈으로 쉽게 인식할 수 있는 문자열로 바꿔줍니다.
// 성공 시 변환된 문자열의 주소 값, 실패 시 -1 반환
#include <arpa/inet.h>
char* inet_ntoa(struct in_addr adr);
위 함수는 인자로 전달된 정수형태의 IP 정보를 참조하여 문자열 형태의 IP 정보로 변환해서 변환된 문자열의 주소 값을 반환합니다. 이 함수는 내부적으로 메로리공간을 할당해서 변환된 문자열 정보를 저장합니다. 따라서 이 함수호출 후에는 가급적 반환된 문자열 정보를 다른 메모리 공간에 복사해두는 것이 좋습니다. 다시 한 번 inet_ntoa 함수가 호출되면, 전에 저장된 문자열 정보가 지워질 수 있기 때문입니다. 다음 예제 코드를 보며 사용법을 익혀보겠습니다.
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, char* argv[]) {
struct sockaddr_in addr1, addr2;
char* str_ptr;
char str_arr[20];
addr1.sin_addr.s_addr = htonl(0x1020304);
addr2.sin_addr.s_addr = htonl(0x1010101);
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: %s\n", str_ptr);
printf("Dotted-Decimal notation3: %s\n", str_arr);
return 0;
}
결과는 아래와 같습니다.
결과를 보면, addr1.sin_addr를 전달해줘서 str_ptr에 반환된 값을 저장해뒀는데, 이후에 inet_ntoa 함수가 다시 호출된 후 str_ptr에 있는 문자열을 확인해보면 다른 값이 들어 있는 것을 알 수 있습니다. 이런식으로 inet_ntoa함수를 호출하면 원래 갖고 있던 문자열 정보를 잃을 수 있으니 호출하기 전 필요하다면 미리 문자열 정보를 저장해둬야 합니다.
인터넷 주소의 초기화
여태까지 다뤄본 내용을 기반으로 소켓생성과정에서 흔히 등장하는 인터넷 주소정보의 초기화 방법을 보이겠습니다.
struct sockaddr_in addr;
char* serv_ip = "211.217.168.13";
char* serv_port = "9190";
memset(&addr, 0, sizeof(addr)); // 구조체 변수 addr의 모든 멤버 0으로 초기화
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주소를 입력하는 것이 귀찮다면 아래와 같이 주소정보를 초기화해도 됩니다.
struct sockaddr_in addr;
char* serv_port = "9190";
memset(&addr, 0, sizeof(addr)); // 구조체 변수 addr의 모든 멤버 0으로 초기화
addr.sin_family = AF_INET; // 주소체계 지정
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 소켓이 동작하는 컴퓨터의 IP주소 할당
addr.sin_port = htons(atoi(serv_port)); // 문자열 기반의 PORT 번호 초기화
위 주석에 적어놓았듯이, 위와 같이 INADDR_ANY를 이용하면 소켓이 동작하는 컴퓨터의 IP주소를 자동으로 할당해줍니다. 또한 컴퓨터 내에 두 개 이상의 IP를 할당 받아서 사용하는 경우 어떤 주소를 통해서 데이터가 들어와도 PORT 번호만 일치하면 수신할 수 있게 됩니다. 따라서 서버 프로그램의 구현에 많이 선호되는 방법입니다.
소켓에 인터넷 주소 할당하기
구조체 sockaddr_in의 변수 초기화를 마쳤으니, 초기화된 주소정보를 소켓에 할당해야합니다.
다음에 소개하는 bind 함수가 이를 담당합니다. 이 함수는 다음과 같은 인자들을 전달받습니다.
- inf sockfd: 주소정보를 할당할 소켓의 파일 디스크립터
- struct sockaddr* myaddr: 할당하고자 하는 주소정보를 지니는 구조체 변수의 주소 값
- socklen_t addrlen: 두 번째 인자로 전달된 구조체 변수의 길이정보
// 성공 시 0, 실패 시 -1 반환
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
이제 지금까지 설명한 것들을 바탕으로 서버 프로그램에서 흔히 등장하는 서버 소켓 초기화의 과정을 보이겠습니다.
int serv_sock;
struct sockaddr_in serv_addr;
char* serv_port = "9190";
/* 서버 소켓(리스닝 소켓) 생성 */
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
/* 주소정보 초기화 */
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(serv_port));
/* 주소정보 할당 */
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
'네트워크 프로그래밍 > C' 카테고리의 다른 글
[네트워크 프로그래밍/C] TCP 기반 서버, 클라이언트 구현 (0) | 2022.02.22 |
---|---|
[네트워크 프로그래밍/C] TCP와 UDP에 대한 이해 (0) | 2022.02.22 |
[네트워크 프로그래밍/C] 네트워크 바이트 순서와 인터넷 주소 변환 (0) | 2022.02.21 |
[네트워크 프로그래밍/C] 주소정보의 표현 (0) | 2022.02.17 |
[네트워크 프로그래밍/C] 소켓에 할당되는 IP주소와 PORT 번호 (0) | 2022.02.17 |