- 인터넷상에서 컴퓨터를 구분하는 목적으로 사용되는 주소
- 4byte 주소 체계인 IPv4, 16byte 주소 체계인 IPv6가 존재한다.
- 소켓을 생성할 때 기본적인 프로토콜을 지정해야 한다.
- 네트워크 주소와 호스트 주소로 나뉜다.
네트워크 주소를 이용해서 네트워크를 찾고, 호스트 주소를 이용해서 해당 네트워크에서 호스트를 구분한다. (subnet mask)- netmask를 가지고 비트 & 연산을 하면 자기의 네트워크 소속인 지 알 수있다.


A Class : 대규모 네트워크 환경, 첫 번째 마디의 숫자가 0 ~ 127 ex) 12.111.111.111
B Class : 중규모 네트워크 환경, 첫 번째 마디의 숫자가 128 ~ 191, ex) 128.111.111.111
C Class : 소규모 네트워크 환경, 첫 번째 마디의 숫자가 192 ~ 223, ex) 192.168.0.1
D Class : 멀티캐스팅용도, 잘 쓰이지 않음.
E Class : 연구/개발용 혹은 미래에 사용하기 위해 남겨놓은 클래스, 일반적인 용도로 사용 X
1byte = 8bit, 2^8 = 256 -> 범위는 [0,255]

- IP는 컴퓨터를 구분하는 용도로 사용되며, Port 번호는 소켓(소켓에 연결된 응용프로그램)을 구분하는 용도로 사용
- 하나의 프로그램 내에서는 둘 이상의 소켓이 존재할 수 있으므로, 둘 이상의 Port가 하나의 프로그램에 의해 할당될 수 있다.
- Port번호는 2Byte (16 bits)로 표현, 값은 0 ~ 65535 이하 즉, 포트 번호는 short 크기
- 0 ~ 1023은 Well-known PORT로 이미 용도가 결정되어있음.

struct sockaddr_in { sa_family_t sin_family; // 주소 체계, 2byte uint16_t sin_port; //Port, 번호 2byte struct in_addr sin_addr; //32비트 IP주소, 4byte char sin_zero[8]; // 사용 X, 8byte }

sockaddr_in 구조체 멤버
struct sockaddr_in serv_addr;
.....
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
(struct sockaddr*) &serv_addr 부분을 주목해보자.
구조체 변수 sockaddr_in을 bind 함수의 인자로 전달해야 한다.
여기서 bind() 함수의 매개변수 타입이 sockaddr이므로 형 변환이 필요

sa_faily_t의 크기는 short(2byte), char형 배열의 크기는 14byte
구조체의 크기는 16byte
CPU는 데이터를 메모리에 저장하는 방식을 두 가지로 나눈다.
CPU가 데이터를 저장하는 방식이 다르면 해석하는 방법도 두 가지로 나뉜다는 뜻이다.
예를 들어 0x20번지를 시작으로 4바이트 int형 정수 0x12345678을 저장한다고 가정하자.

여기서 문제가 되는 점은 빅 엔디안 시스템과 리틀 엔디안 시스템끼리의 데이터 통신과정에서 발생한다.

그래서 통일된 데이터 송수신 기준이 필요하다.
네트워크 바이트 순서
- 통일된 데이터 송수신 기준을 의미
- 빅 엔디안이 기준
- 데이터 송수신 과정 : 바이트 단위로 전송하기 때문에 바이트 변환 과정이 필요 없음


unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
htons는 호스트 port 번호를 네트워크 port번호로 변환
ntohs는 네트워크 port 번호를 호스트 port번호로 변환
htonl는 호스트 IP 주소를 네트워크 IP주소로 변환
ntohl은 네트워크 IP 주소를 호스트 IP주소로 변환
바이트 순서 변환은 sockaddr_in 구조체 변수에 데이터를 저장할 때 사용하면 된다.
#include <stdio.h>
#include <arpa/inet.h>
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);
return 0;
}
/*
Host ordered port : 0x1234
Network Ordered port : 0x3412
Host ordered address : 0x12345678
Network Ordered address : 0x78563412
Network 를 보았을 때 이 컴퓨터가 Little Endian임을 의미한다.
Big Endian의 시스템의 CPU였으면 변환 이후에도 값은 달라지지 않는다.
즉, Little Endian 기준의 CPU라면 함수 실행 후 바이트 순서가 바뀐 값이 반환된다.
*/
문자열 정보를 네트워크 바이트 순서의 정수로 변환할 필요가 있다.
sockaddr_in 구조체 안에 주소 정보를 저장하기 위해 선언된 멤버는 32비트 정수형으로 정의되어 있다. IP주소 정보의 할당을 위해서 32bit 정수형태로 IP주소를 표현할 수 있어야 한다.
첫 번째 방법
#include <arpa/inet.h>
in_addr_t inet_addr(const char * string);
/*
성공 시 빅 엔디안으로 변환된 32bit 정수 값, 실패시 INADDR_NONE 반환
*/


두 번째 방법
int inet_aton(const char * string, struct in_addr * addr);
/*
성공 시 1(true), 실패시 0(false) 반환
string : 변환할 IP주소 정보를 담고 있는 문자열의 주소 값 전달
addr : 변환된 정보를 저장할 in_addr 구조체 변수의 주소 값 전달
*/
inet_addr과의 차이는 구조체 변수 in_addr을 이용하는 형태의 차이다.
활용도는 inet_aton 함수가 더 높다.
실제 코드 작성 과정에서는 inet_addr 함수를 사용할 경우 변환된 IP주소 정보를 구조체 sockaddr_in에 선언되어 있는 in_addr 구조체 변수에 대입하는 과정을 추가로 거쳐야 한다.
inet_aton 함수는 별도의 대입과정을 거칠 필요가 없다.
인자로 in_addr 구조체 변수의 주소 값을 전달하면 자동으로 변환된 값이 구조체 변수에 저장된다.


네트워크 바이트 순서로 정렬된 IP주소 정보를 눈으로 쉽게 인식할 수 있는 문자열의 형태로 변환해준다.
위에 함수들과 반대의 기능
#include <arpa/inet.h>
char * inet_ntoa(struct in_addr adr);
/*
성공 시 변환되 문자열의 주소 값, 실패시 -1 반환
*/
인자로 전달된 정수형태의 IP정보를 참조하여 문자열 형태의 IP정보로 반환해서 변환된 문자열의 주소 값을 반환한다.
반환형이 char형 포인터라는 뜻은 문자열이 메모리 공간에 저장되었다는 뜻이다.
함수 내부적으로 메모리 공간을 할당해서 변환된 문자열 정보를 저장하기 때문에 이 함수를
추후에 다시 호출할 경우 그 공간을 다시 사용하여 원래 저장한 문자열이 사라질 확률이 있다.
그렇기에 해당 함수를 사용할 때는 항상 반환받은 문자열의 주소값을 별도의 메모리 공간에 복사를 해둬야 한다.


위에 내용을 토대로 소켓생성과정에서 등장하는 인터넷 주소정보를 초기화해보자
// 인터넷 주소 정보 초기화
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으로 초기화 하는 이유는 sin_zero를 0으로 초기화하기 위해서
atoi는 문자열로 저장된 port를 숫자로 변환하기 위해서
실제로는 IP와 PORT를 저렇게 초기화하고 사용하면 안 된다.
다른 컴퓨터에서 사용하려면 계속 바꿔줘야 하기 때문이다.
*/
서버는 해당 문장이 bind 함수를 통해서 이루어지고
클라이언트에서는 connect 함수에서 이루어진다.
서버에서는 sockaddr_in 구조체 변수를 하나 선언해서, 이를 서버 소켓이 동작하는 컴퓨터의 IP와 소켓에 부여할 PORT번호로 초기화한 다음에 bind 함수를 호출한다.
클라이언트에서는 sockaddr_in 구조체 변수를 하나 선언해서, 이를 연결한 서버 소켓의 IP와 PORT번호로 초기화한 다음에 connect 함수를 호출한다.
- 소켓이 동작하는 컴퓨터의 IP주소가 자동으로 할당
- 컴퓨터 내에 두 개 이상의 IP주소를 할당 받아서 사용하는 경우
어떤 주소를 통해 데이터가 들어오더라도 PORT 번호만 일치하면 수신함

- 서버 소켓 생성시 IP주소가 필요한 이유
서버 소켓은 생성시에 자신이 속한 컴퓨터의 IP주소로 초기화가 이루어져야 한다.
즉, 초기화할 IP주소가 뻔하지만 초기화를 하는 이유는 다음과 같다.
하나의 컴퓨터에 둘 이상의 IP주소가 할당될 수 있다.
IP주소는 컴퓨터에 장착된 NIC(랜카드)의 개수만큼 부여가 가능하다.
이러한 경우에는 서버 소켓이라 할지라도 어느 IP주소로 들어오는(어느 NIC으로 들어오는) 데이터를 수신할 지 결정해야 한다. 때문에 서버 소켓이라 할지라도 어느 IP주소로 들어오는 데이터를 수신할지 결정해야 한다.
이러한 이유로 서버 소켓의 초기화 과정에서 IP주소 정보를 요구하는 것이다.
반면 NIC가 하나뿐인 컴퓨터라면 INADDR_ANY를 이용해서 초기화하는 것이 편리하다.


참고 : 윤성우의 열혈 TCP/IP 소켓 프로그래밍