책정리/열혈 TCP,IP

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

GONII 2015. 8. 5. 16:08
  • 소켓에 할당되는 IP주소와 PORT번호

    IP는 Internet Protocol의 약자로 인터넷상에서 데이터를 송수신할 목적으로 컴퓨터에게 부여하는 값을 의미한다. 반면 PORT번호는 컴퓨터에게 부여하는 값이 아닌, 프로그램상에서 생성되는 소켓을 구분하기 위해 소켓에 부여되는 번호를 뜻한다

    • 인터넷 주소(Internet Address)

      인터넷에 컴퓨터를 연결해서 데이터를 주고받기 위해서는 IP주소를 부여 받아야 한다. IP주소체계는 다음과 같이 두 종류로 나뉜다

IPv4(Internet Protocol version 4)

4바이트 주소체계

IPv6(Internet Protocol version 6)

16바이트 주소체계

IPv4 기준의 4바이트 IP주소는 네트워크 주소와 호스트(컴퓨터를 의미함) 주소로 나뉘며, 주소의 형태에 따라서 A, B, C, D, E 클래스로 분류가 된다. 클래스 E는 일반적이지 않은 예약되어 있는 주소체계이다

네트워크 주소(네트워크 ID)란 네트워크의 구분을 위한 IP주소의 일부를 말한다. 예를 들어 WWW.SEMI.COM 이라는 회사의 무대리에게 데이터를 전송한다고 가정해보자. 그런데 이 회사의 컴퓨터는 하나의 로컬 네트워크로 연결되어있다. 그렇다면 먼저 SEMI.COM의 네트워크로 데이터를 전송하는 것이 우선이다. 즉, 처음부터 4바이트 IP주소 전부를 참조해서 무대리의 컴퓨터로 데이터가 전송되는 것이 아니라, 4바이트 IP주소 중에서 네트워크 주소만을 참조해서 일단 SEMI.COM의 네트워크로 데이터가 전송된다. 그리고 SEMI.COM의 네트워크로 데이터가 전송되었다면, 해당 네트워크는 전송된 데이터의 호스트 주소(호스트 ID)를 참조하여 무대리의 컴퓨터로 데이터를 전송해준다.

위 그림에서 보면 임의의 호스트가 203.211.172.103과 203.211.217.202로 데이터를 전송하고 있다. 그런데 이 중에서 203.211.172와 203.211.217이 네트워크 주소이다. 따라서 해당 네트워크로 데이터가 전송된다. 단, 네트워크로 데이터가 전송된다는 것은 네트워크를 구성하는 라우터(Router)또는 스위치(Switch)로 데이터가 전송됨을 뜻한다. 그러면 데이터를 전송 받은 라우터는 데이터에 적혀있는 호스트 주소를 참조하여 호스트에 데이터를 전송해준다.

  • 클래스 별 네트워크 주소와 호스트 주소의 경계

    IP주소의 첫 번째 바이트만 보면 네트워크 주소가 몇 바이트인지 판단이 가능하다. 왜냐하면 다음과 같이 클래스 별 IP주소의 경계를 나눠놓았기 때문이다.

클래스 A의 첫 번째 바이트 범위

0이상 127이하

클래스 B의 첫 번째 바이트 범위

128이상 191이하

클래스 C의 첫 번째 바이트 범위

192이상 223이하

이는 다음과 같이 달리 표현할 수도 있다

클래스 A의 첫 번째 비트는 항상 0으로 시작

클래스 B의 첫 두 비트는 항상 10으로 시작

클래스 C의 첫 세 비트는 항상 110으로 시작

이러한 기준이 정해져 있기 때문에 소켓을 통해서 데이터를 송수신할 때, 우리가 별도로 신경 쓰지 않아도 네트워크로 데이터가 이동하고 이어서 최종 목적지인 호스트로 데이터가 전송되는 것이다.

  • 소켓의 구분에 활용되는 PORT번호

    IP는 컴퓨터를 구분하기 위한 목적으로 존재한다. 때문에 IP만 있다면 목적지 컴퓨터로 데이터를 전송할 순 있다. 그러나 이것만 가지고 데이터를 수신해야 하는 최종 목적지인 응용프로그램에까지 데이터를 전송할 순 없다.

    컴퓨터에는 NIC(네트워크 인터페이스 카드)라 불리는 데이터 송수신장치가 하나씩 달려있다. IP는 데이터를 NIC을 통해 컴퓨터 내부로 전송하는데 사용된다. 그러나 컴퓨터 내부로 전송된 데이터를 소켓에 적절히 분배하는 작업은 운영체제가 담당한다. 이 때 운영체제는 PORT 번호를 활용한다. 즉 NIC을 통해 수신된 데이터 안에는 PORT번호가 새겨져 있다. 운영체제는 바로 이 정보를 참조해서 일치하는 PORT번호의 소켓에 데이터를 전달하는 것이다.

    이렇듯 PORT번호는 하나의 운영체제 내에서 소켓을 구분하는 목적으로 사용되기 때문에 하나의 운영체제 내에서 동일한 PORT번호를 둘 이상의 소켓에 할당할 수 없다. 그리고 PORT번호는 16비트로 표현된다. 때문에 할당할 수 있는 PORT번호의 범위는 0이상 65535이하이다. 그러나 0부터 1023번까지는 '잘 알려진 PORT(Well-known PORT)'라 해서, 특정 프로그램에 할당하기로 예약되었기 때문에, 이 범위의 값을 제외한 다른 값을 할당해야 한다. 그리고 PORT번호는 중복이 가능하지만, TCP소켓과 UDP소켓은 PORT번호를 공유하지 않기 때문에 중복되어도 상관없다. 즉 TCP소켓을 생성할 때 9190 PORT번호를 할당했다면, 다른 TCP소켓에는 9190 PORT번호를 할당할 수 없지만, UDP소켓에는 할당할 수 있다

    정리하면 우리가 흔히 말하는 데이터 전송의 목적지 주소에는 IP주소뿐만 아니라 PORT번호도 포함이 된다. 그래야 최종 목적지에 해당하는 응용프로그램에까지 데이터를 전달할 수 있기 때문이다.

  • 주소정보의 표현

    응용 프로그램상에서의 IP주소와 PORT번호 표현을 위한 구조체가 정의되어 있다.

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

      주소 정보를 담을 때는 다음 세 가지 물음에 답이 되도록 담아야 한다

      • 어떤 주소 체계를 사용하는지
      • IP주소가 어떻게 되는지
      • PORT번호가 어떻게 되는지

      이 구조체는 bind 함수에 주소 정보를 전달하는 용도로 사용된다

      struct sockaddr_in

      {

      sa_family_t                sin_family;        // 주소체계(Address Family)

      uint16_t                sin_port;        // 16비트 TCP/UDP PORT 번호

      struct in_addr        sin_addr;        // 32비트 IP주소

      char                        sin_zero[8]; // 사용되지 않음

      };

      그리고 위의 구조체 정의에 사용된 또 다른 구조체 in_addr은 다음과 같이 정의되어 있다. 이는 IP주소정보를 담을 수 있도록 정의되어 있다.

      struct in_addr

      {

      in_addr        sin_addr;        // 32비트 IPv4 인터넷 주소

      };

      위의 두 구조체 설명에 앞서 생소한 자료형에 대한 소개가 먼저 필요해 보인다. uint16_t, in_addr_t와 같은 자료형의 근거는 POSIX(Potable Operation System Interface)에서 찾을 수 있다. POSIX란 유닉스 계열의 운영체제에 적용하기 위한 표준을 의미한다. 즉 POSIX에서는 다음과 같이 추가로 자료형을 정의하고 있다.

자료형 이름

자료형에 담길 정보

선언된 헤더파일

int8_t
uint8_t
int16_t
uint16_t
int32_t
uint32_t

singed 8-bit int
unsigned 8-bit int(unsigned char)
signed 16-bit int

unsigned 16-bit int(unsigned short)

signed 32-bit int

unsigned 32-bit int(unsigned long)

sys/types.h

sa_family_t
socklen_t

주소체계(address family)

길이정보(length of struct)

sys/socket.h

in_addr_t
in_port_t

IP주소정보, uint32_t로 정의되어 있음
PORT 번호 정보, uint16_t로 정의되어 있음

netinet/in.h

이러한 식으로 자료형을 별도로 정의해놓은 것은 확장성을 고려한 결과이다. 즉 int32_t라는 자료형을 사용한다면 이는 어떠한 경우에도 4바이트 자료형임을 보장받을 수 있다.

  • 구조체 sockaddr_in 멤버에 대한 분석
    • 멤버 sin_family

      프로토콜 체계마다 적용하는 주소체계가 다르다. 예를 들어서 IPv4에서는 4바이트 주소체계를 사용하고 IPv6에서는 16바이트 주소체계를 사용한다. 따라서 아래의 표를 참조하여 멤버 sin_family에 적용할 주소체계 정보를 저장해야 한다

주소체계(Address Family)

의미

AF_INET

IPv4 인터넷 프로토콜에 적용하는 주소체계

AF_INET6

IPv6 인터넷 프로토콜에 적용하는 주소체계

AF_LOCAL

로컬 통신을 위한 유닉스 프로토콜의 주소체계

  • 멤버 sin_port

    16비트 PORT번호를 저장한다. 단, '네트워크 바이트 순서'로 저장해야 한다

  • 멤버 sin_addr

    32비트 IP주소정보를 저장한다. 이 역시 '네트워크 바이트 순서'로 저장해야 한다. 이 멤버를 정확히 파악하기 위해서는 구조체 in_addr도 함께 살펴봐야 한다. 그런데 구조체 in_addr의 유일한 멤버가 uint32_t로 선언되어 있으니 간단히 32비트 정수자료형으로 인식해도 괜찮다.

  • 멤버 sin_zero

    특별한 의미를 지니지 않는 멤버이다. 단순히 구조체 sockaddr_in의 크기를 구조체 sockaddr와 일치시키기 위해 삽입된 멤버이다. 반드시 0으로 채워야 한다

   

sockaddr_in 구조체 변수의 주소 값은 bind 함수의 인자로 다음과 같이 전달된다.

struct sockaddr_in serv_addr;

.....

if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)

error_handling("bind() error");

......

bind 함수는 sockaddr 구조체 변수의 주소 값을 요구한다. 앞서 설명한 주소체계, PORT번호, IP주소정보를 담고 있는 sockaddr 구조체 변수의 주소값을 요구하는 것이다. 그런데 아래에서 보이듯이 구조체 sockaddr은 이들 정보를 담기에 다소 불편하게 정의되어 있다.

struct sockaddr

{

sa_family_t                sin_family;        // 주소체계(Address Family)

char                        sa_data[14];        // 주소 정보

};

위의 구조체 멤버 sa_data에 저장되는 주소정보에는 IP주소와 PORT번호가 포함되어야 하고, 이 두 가지 정보를 담고 남은 부분은 0으로 채울 것을 bind함수는 요구하고 있다. 그런데 이는 주소정보를 담기에 매우 불편한 요구사항이다. 그래서 구조체 sockaddr_in이 등장한 것이다. sockaddr_in 구조체 멤버를 앞서 설명한대로 채우면, 이 때 형성되는 구조체 변수의 바이트 열이 bind 함수가 요구하는 바이트 열이 된다. 결국 인자전달을 위한 형변환을 통해서 sockaddr 구조체 변수에 bind함수가 요구하는 바대로 데이터를 채워넣는 효과를 볼 수 있다.

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

    4바이트 정수 1을 2진수로 표현하면 다음과 같다

    00000000 00000000 00000000 00000001

    이 순서 그대로 메모리에 저장하는 CPU가 있는가 하면, 다음과 같이 거꾸로 저장하는 CPU도 있다

    00000001 00000000 00000000 00000000

    때문에 이러한 부분을 고려하지 않고서 데이터를 송수신하면 문제가 발생할 수 있다. 저장순서가 다르다는 것은 전송되어온 데이터의 해석순서가 다름을 뜻하기 때문이다.

    • 바이트 순서(Order)와 네트워크 바이트 순서

      CPU가 데이터를 메모리에 저장하는 방식은 다음과 같이 두 가지로 나뉜다. 참고로 CPU가 데이터를 메모리에 저장하는 방식이 두 가지로 나뉜다는 것은 CPU가 데이터를 해석하는 방식도 두 가지로 나뉜다는 뜻이다.

빅엔디안(Big Endian)

상위 바이트의 값을 작은 번지수에 저장하는 방식

리틀 엔디안(Little Endian)

상위 바이트의 값을 큰 번지수에 저장하는 방식

0x20번지를 시작으로 4바이트 int형 정수 0x12345678을 저장한다고 가정해 보자 빅 엔디안 방식의 CPU는 다음의 형태로 메모리에 저장한다

반면 리틀 엔디안 방식은 위의 그림과 반대의 순서로 저장된다.

이렇듯 데이터 저장방식은 CPU마다 다르다. 그래서 CPU의 데이터 저장방식을 의미하는 '호스트 바이트 순서(Host Byte Order)'는 CPU에 따라서 차이가 난다. 참고로 여러분이 주로 사용하는 인텔계열 CPU는 리틀 엔디안 방식으로 데이터를 저장한다.

빅 엔디안 시스템에서 0x12, 0x34의 조합으로 만들어지는 값은 리틀 엔디안 시스템에서 0x34, 0x12의 조합으로 만들어지는 값과 같다. 즉, 저장되는 순서가 바뀌어야 동일한 값으로 인식된다. 그런데 위 그림에서는 빅 엔디안 시스템에 저장된 값 0x1234를 리틀 엔디안 시스템에 전송하는데, 바이트 순서에 대한 문제를 고려하지 않고 0x12, 0x34의 순으로 데이터를 전송하고 있다. 결국 리틀 엔디안 시스템은 전송되는 순서대로 데이터를 저장한다. 때문에 전송된 값은 리틀 엔디안 입장에서 0x1234가 아닌 0x3412가 되어버린다. 바로 이러한 문제점 때문에 네트워크를 통해서 데이터를 전송할 때에는 통일된 기준으로 데이터를 전송하기로 약속하였으며, 이 약속을 가리켜 '네트워크 바이트 순서(Network Byte Order)'라 한다.

네트워크 바이트 순서의 약속은 빅 엔디안 방식으로 통일한다

즉, 네트워크상으로 데이터를 전송할 때에는 데이터의 배열을 빅 엔디안 기준으로 변경해서 송수신하기로 약속한 것이다. 때문에 모든 컴퓨터는 수신된 데이터가 네트워크 바이트 순서로 정렬되어 있음을 인식해야 하며, 리틀 엔디안 시스템에서는 데이터를 전송하기에 앞서 빅 엔디안으로 정렬방식으로 데이터를 재정렬 해야 한다.

  • 바이트 순서의 변환(Endian Conversions)

    바이트 순서의 변환을 돕는 함수는 다음과 같다

    • unsigned short htons(unsigned short);
    • unsigned short ntohs(unsigned short);
    • unsigned long htonl(unsigned long);
    • unsigned long ntohl(unsigned long);

    h는 호스트(host)바이트 순서를 의미한다

    n은 네트워크(network)바이트 순서를 의미한다

    s는 short, l은 long을 의미한다

    따라서 htons는 h, to, n, s의 조합으로 다음과 같이 해석할 수 있다

    "short형 데이터를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환해라"

    일반적으로 뒤에 s가 붙는 함수는 s가 2바이트 short를 의미하므로 PORT번호의 변환에 사용되고, 뒤에 l이 붙는 함수는 4바이트 long을 의미하므로 IP주소의 변환에 사용된다.

    • 예제 endian_conv.c

#include <stdio.h>

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

int main(int argc, char* argv[])

{

unsigned short host_port = 0x1234;

unsigned short net_port;

unsigned long host_addr = 0x12345678;

unsigned long net_addr;

   

net_port = htons(host_port);

net_addr = htonl(host_addr);

   

printf("Host ordered port : %#x\n", host_port);

printf("Network ordered port : %#x\n", net_port);

printf("Host ordered address : %#lx\n", host_addr);

printf("Network ordered address : %#lx\n", net_addr);

}

  • 인터넷 주소의 초기화와 할당
    • 문자열 정보를 네트워크 바이트 순서의 정수로 변환하기

      sockaddr_in 안에서 주소정보를 저장하기 위해 선언된 멤버는 32비트 정수형으로 정의되어 있다. 따라서 우리는 IP주소 정보의 할당을 위해서 32비트 정수형태로 IP주소를 표현할 수 있어야 한다. 그러나 문자열 정보에 익숙한 우리들에게 이는 만만치 않은 일이다.

      그런데 다행히도 문자열로 표현된 IP주소를 32비트 정수형으로 변환해주는 함수가 있다. 뿐만 아니라, 이 함수는 변환과정에서 네트워크 바이트 순서로의 변환도 동시에 진행된다.

#include <arpa/inet.h>

in_addr_t inet_addr(const char* string);

// 성공 : 빅 엔디안으로 변환된 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 = inet_addr(addr1);

if( conv_addr == INADDR_NONE )

printf("Error occured!\n";

else

printf("Network ordered integer addr: %#1x\n", conv_addr);

   

conv_addr = inet_addr(addr2);

if(conv_addr == INADDR_NONE)

printf("error occureded\n");

else

printf("Network ordered integer addr: %#1x\n\n", conv_addr);

   

return 0;

}

inet_addr 함수는 32비트 정수형태로 IP주소를 변환할 뿐만 아니라, 유효 하지 못한 IP주소에 대한 오류검출 능력도 갖고 있다. 그리고 출력결과를 통해서 네트워크 바이트 순서로 정렬되었음도 확인할 수 있다.

inet_aton 함수도 기능상으로 inet_addr 함수와 동일하다. 즉, 문자열 형태의 IP주소를 32비트 정수, 그것도 네트워크 바이트 순서로 정렬해서 반환한다. 다만 구조체 변수 in_addr를 이용하는 형태라는 점에서 차이를 보인다.

#include <arpa/inet.h>

   

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

// 성공 : 1(true) 반환

// 실패 : 0(false) 반환

실제 코드 작성과정에서 inet_addr 함수를 사용할 경우, 변환된 IP주소 정보를 구조체 sockaddr_in에 선언되어 있는 in_addr 구조체 변수에 대입하는 과정을 추가로 거쳐야 한다. 그러나 위 함수를 사용할 경우 별도의 대입과정을 거칠 필요가 없다. 인자로 in_addr 구조체 변수의 주소 값을 전달하면, 변환된 값이 자동으로 in_addr 구조체 변수에 저장되기 때문이다.

  • 예제 inet_aton.c

#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.s_addr);

   

return 0;

}

   

void error_handling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

이제 마지막으로 inet_aton 함수의 반대기능을 제공하는 함수 하나를 소개하겠다. 이 함수는 네트워크 바이트 순서로 정렬된 정수형 IP주소 정보를 우리가 눈으로 쉽게 인식할 수 있는 문자열의 형태로 변환해준다.

#include <arpa/inet.h>

   

char* inet_ntoa(struct in_addr adr);

// 성공 : 변환된 문자열의 주소 값

// 실패 : -1

  • 예제 inet_ntoa.c

#include <stdio.h>

#include <string.h>

#include <arpa/inet.h>

   

int main(int argc, char* argv[])

{

strcut sockaddr_int 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;

}

  • 인터넷 주소의 초기화

    struct sockaddr_in addr;

    char* serv_ip = "211.217.168.13";        // IP주소 문자열 선언

    char* serv_port = "9190";                // PORT번호 문자열 선언

    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 번호 초기화

       

    memset 함수를 호출해서 addr을 전부 0으로 초기화하는 이유는, 0으로 초기화해야 하는 sockaddr_in 구조체 멤버 sin_zero를 0으로 초기화하기 위함이다. 그리고 위의 코드 마지막 문장에서 호출하는 atoi 함수는 문자열로 표현되어 있는 값을 정수로 변환해서 반환한다. 결론적으로 위의 코드에서는 문자열로 표현된 IP주소와 PORT번호를 기반으로 하는 sockaddr_in 구조체 변수의 초기화 과정을 보인 것이다.

  • 클라이언트의 주소 정보 초기화

    클라이언트 프로그램에서는 sockaddr_in 구조체 변수를 하나 선언해서, 이를 연결할 서버 소켓의 IP와 PORT번호로 초기화한 다음에 connect함수를 호출한다

  • INADDR_ANY

    struct sockaddr_in addr;

    char* serv_port = "9190";

    memset(&addr, 0, sizeof(addr));

    addr.sin_family = AF_INET;

    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    addr.sin_port = htons(atoi(serv_port));

    앞서 소개한 방식과 가장 큰 차이점은 INADDR_ANY라는 이름의 상수를 통해서 서버의 IP주소를 할당하고 있다는 점이다. 소켓의 IP주소를 이렇게 초기화할 경우 소켓이 동작하는 컴퓨터의 IP주소가 자동으로 할당되기 때문에 IP주소를 직접 입력하는 수고를 덜 수 있다. 뿐만 아니라 컴퓨터 내에 두 개 이상의 IP를 할당 받아서 사용하는 경우, 할당 받은 IP중 어떤 주소를 통해서 데이터가 들어오더라도 PORT번호만 일치하면 수신할 수 있게 된다. 따라서 서버 프로그램의 구현에 많이 선호되는 방법이다.

  • chapter01의 hello_server.c, hello_client.c의 실행에 대한 고찰

    ./hserver 9190

    hell_server.c 의 실행을 위해서 다음의 명령문을 전달했다

    main함수에 전달된 9190은 PORT번호이다. 반대로 소켓의 IP주소는 전달하지 않았는데, 그 이유는 INADDR_ANY를 통한 IP주소의 초기화에서 찾을 수 있다.

       

    ./hclient 127.0.0.1 9190

    127.0.0.1을 가리켜 '루프백 주소(loopback address)'라 하며 이는 컴퓨터 자신의 IP주소를 의미한다. 이를 대신해서 실제 컴퓨터의 IP주소를 입력해도 프로그램은 동작한다. 뿐만 아니라, 서버와 클라이언트를 서로 다른 두 대의 컴퓨터에서 각각 실행할 경우에는 이를 대신해서 서버의 IP주소를 입력하면 된다.

  • 소켓에 인터넷 주소 할당하기

    구조체 sockaddr_in의 변수초기화 방법에 대해서 살펴보았으니, 이제 초기화된 주소 정보를 소켓에 할당하는 일만 남았다.

#include <sys/socket.h>

   

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

// 성공 : 0

// 실패 : -1

위의 함수호출이 성공하면, 첫 번째 인자에 해당하는 소켓에 두 번째 인자로 전달된 주소정보가 할당된다.

   

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));

..........

  • 윈도우 기반으로 구현하기
    • 함수 htons, htonl의 윈도우 기반 사용 예

#include <stdio.h>

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

void errorhandling(char* message);

   

int main(int argc, char* argv[])

{

WSADATA wsaData;

unsigned short host_port = 0x1234;

unsigned short net_port;

unsigned long host_addr = 0x12345678;

unsigned long net_addr;

   

if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0 )

errorhandling("WSAStartup() error");

   

net_port = htons(host_port);

net_addr = htonl(host_addr);

   

printf("Host ordered port : %#x\n", host_port);

printf("Network ordered port: %#x\n", net_port);

printf("Host ordered port : %#lx\n", host_addr);

printf("Network ordered port: %#lx\n", net_addr);

   

WSACleanup();

return 0;

}

   

void errorhandling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • 함수 inet_addr, inet_ntoa의 윈도우 기반 사용 예

#include <stdio.h>

#include <string.h>

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

void errorhandling(char* message);

   

int main(int argc, char* argv[])

{

WSADATA wsaData;

if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0 )

errorhandling("WSAStartup() error");

   

// inet_addr 함수 호출 예

{

char* addr = "127.212.124.78";

unsigned long conv_addr = inet_addr(addr);

if( conv_addr == INADDR_NONE )

printf("error occured\n");

else

printf("Network ordered integer addr : %#lx\n", conv_addr);

}

   

// inet_ntoa 함수 호출의 예

{

struct sockaddr_in addr;

char* strPtr;

char strArr[20];

   

addr.sin_addr.s_addr = htonl(0x1020304);

strPtr = inet_ntoa(addr.sin_addr);

strcpy(strArr, strPtr);

printf("Dotted-Decimal notation3 %s\n", strArr);

}

WSACleanup();

return 0;

}

   

void errorhandling(char* message)

{

fputs(message, stderr);

fputc('\n', stderr);

exit(1);

}

  • 윈도우에서 소켓에 인터넷 주소 할당하기

    SOCKET servSock;

    struct sockaddr_in servAddr;

    char* servPort = "9190";

       

    // 서버 소켓 생성

    servSock = socket(PF_INET, SOCK_STREAM, 0);

       

    // 주소정보 초기화

    memset(&servAddr, 0, sizeof(servAddr));

    servAddr.sin_family = AF_INET;

    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    servAddr.sin_port = htons(atoi(servPort));

       

    // 주소 정보 할당

    bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr));

       

  • WSAStringToAddress & WSAAddressToString

    이 둘은 inet_ntoa, inet_addr함수와 기능은 같으나 다양한 프로토콜에 적용이 가능하다는 장점이 있다. 즉 IPv4 뿐 아니라 IPv6에서도 사용이 가능하다.

    inet_ntoa, inet_addr 함수를 사용할 경우 리눅스 기반에서 윈도우 기반으로, 그리고 그 반대로 프로그램의 변경이 용이하지만, 다음 두 함수를 사용하면 윈도우에 종속적인 코드가 만들어지기 때문에 다른 운영체제로의 이식성이 떨어진다는 단점이 있다.

#include <winsock2.h>

   

INT WSAStringToAddress(

LPSTR AddressString, INT AddressFamily, LPWSAPROTOCOL_INFO lpProtocolInfo, LPSOCKADDR lpAddress, LPINT lpAddressLength);

// 성공 : 0

// 실패 : SOCKET_ERROR

   

#include <winsock2.h>

   

INT WSAAddressToString(

LPSOCKADDR lpsaAddress, DWORD dwAddressLength, LPWSAPROTOCOL_INFO lpProtocolInfo, LPTSTR lpszAddressString, LPDWORD lpdwAddressStringLength);

// 성공 : 0

// 실패 : SOCKET_ERROR

   

예제 conv_addr_win.c

#undef UNICODE

#undef _UNICODE

#include <stdio.h>

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

int main(int argc, char* argv[])

{

char* strAddr = "203.211.218.102:9190";

   

char strAddrBuf[50];

SOCKADDR_IN servAddr;

int size;

   

WSADATA wsaData;

WSAStartup(MAKEWORD(2,2), &wsaData);

   

size = sizeof(servAddr);

WSAStringToAddress(

strAddr, AF_INET, NULL, (SOCKADDR*)&servAddr, &size);

size = sizeof(strAddrBuf);

WSAAddressToString(

(SOCKADDR*)&servAddr, sizeof(servAddr), NULL, strAddrBuf, &size);

   

printf("Second conv result : %s\n", strAddrBuf);

WSACleanup();

   

return 0;

}

 

반응형