소켓에 할당되는 IP주소와 PORT번호
IP: Internet Protocol의 약자로 인터넷상에서 데이터를 송수신할 목적으로 컴퓨터에게 부여하는 값
PORT번호: 컴퓨터에게 부여하는 값이 아닌, 프로그램상에서 생성되는 소켓을 구현하기 위해 소켓이 부여되는 번호
인터넷 주소
인터넷에 컴퓨터를 연결해서 데이터를 주고받기 위해선 IP주소를 부여받아야 한다.
- IPv4(Internet Protocol version 4) 4바이트 주소 체계
- IPv6(Internet Protocol version 6) 16바이트 주소 체계
둘의 차이는 바이트 크기다. 우리가 범용적으로 사용하는 주소는 IPv4 이다. IPv6은 IP주소가 고갈될걸 우려해 만든거지만
아직도 IPv4를 쓰고 있다.
네트워크 주소란?
네트워크의 구분을 위한 IP주소의 일부를 말한다.
예를 들어 어떤 하나의 로컬 네트워크로 연결되어 있는 회사의 A라는 사람에게 데이터를 전송한다고 가정했을 때
우린 이 회사의 네트워크로 데이터를 전송하는 것이 우선 즉 4byte IP주소 모두 참조해서 A라는 사람에게 전송하는것이 아니라
4byte중에 네트워크 주소만을 참조해 그 회사의 네트워크로 데이터가 전송된다 그리고 전송이 완료 되었다면 해당 네트워크는 전송된 데이터의 호스트 주소를 참조하여 A의 컴퓨터로 데이터를 전송해준다.
밑에 그림은 이러한 데이터의 전송과정이다.
위 그림을 보면 호스트가 203.211.172.103 과 203.211.217.202으로 데이터를 전송하고 있다
이 중 203.211.172와 203.211.217이 네트워크 주소 따라서 해당 네트워크로 데이터가 전송된다.
단 네크워크로 데이터가 전송된다는 건 네트워크를 구성하는 라우터 또는 스위치로 데이터가 전송됨을 뜻함
라우터와 스위치
네트워크를 구성하는 외부로부터 수신된 데이터를 호스트에 전달하는 물리적 장치
클래스 별 네트워크 주소와 호스트 주소의 경계
IP주소의 첫 번째 바이트만 딱 보면 네트워크 주소가 몇 바이트인지 판단이 가능하다.
왜냐하면 다음과 같이 클래스 별 IP주소의 경계를 나눠놓았기 때문.
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]
소켓의 구분에 활용되는 Port번호
- IP는 컴퓨터를 구분하는 용도로 사용되며, Port 번호는 소켓(소켓에 연결된 응용프로그램)을 구분하는 용도로 사용
- 하나의 프로그램 내에서는 둘 이상의 소켓이 존재할 수 있으므로, 둘 이상의 Port가 하나의 프로그램에 의해 할당될 수 있다.
- Port번호는 2Byte (16 bits)로 표현, 값은 0 ~ 65535 이하 즉, 포트 번호는 short 크기
- 0 ~ 1023은 Well-known PORT로 이미 용도가 결정되어있음.
IPv4 기반의 주소 표현을 위한 구조체
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
}
- IP주소와 Port번호는 구조체 sockaddr_in의 변수에 담아서 표현
- in_addr 구조체는 IP주소를 가지고 있다.
POSIX에서 정의하고 있는 자료형
POSIX : Portable Operating System Interface + UniX
-> 이식 가능한 운영 체제 인터페이스 + 유닉스 호환 운영체제에 보통 X가 붙는 것에서 유래한다.
서로 다른 Unix 기반 OS의 공통 API를 정리하고 표준화하기 위해서 IEEE에서 책정한 Application Interface 표준 규격이다.
UNIX 계열의 운영체제들이 따르고 있는 CLI(Command Line Interface)의 표준이라고 생각해도 좋다.
실제로 Window를 제외한 대부분의 운영체제가 POSIX 표준을 따른다고 한다. (MacOS도 표준을 따르고 있다.)
(MacOS의 CLI 명령어는 리눅스와 동일하고, Window PowerShell은 명령어가 다르다는 것을 알고 있을 것이다.)
IEEE란?
IEEE Computer Society라는 컴퓨팅(하드웨어, 소프트웨어, 표준 등)을 전문으로 하는 전기전자공학회(IEEE)의 기술 학회이다.
구조체 sockaddr_in 멤버에 대한 분석
sin_family
주소 체계 정보 저장
주소체계 | 의미 |
AF_INET | IPv4 인터넷 프로토콜에 적용하는 주소체계 |
AF_INET6 | IPv6 인터넷 프로토콜에 적용하는 주소체계 |
AF_LOCAL | 로컬 통신을 위한 유닉스 프로토콜의 주소체계 |
sin_port
- 16bit Port 번호 저장
- 네트워크 바이트 순서로 저장
sin_addr
- 32bit IP주소 정보 저장
- 네트워크 바이트 순서로 저장
- 멤버 sin_addr의 구조체 자료형 in_addr, 사실상 32bit 정수 자료형이다.
sin_zero
- 특별한 의미를 지니지 않는다.
- 반드시 0으로 채워야 한다.
- 특별한 의미를 지니지 않는데 있는 이유는 구조체 sockaddr과 관련이 있다.
- 자료형 struct sockaddr의 크기를 맞춰야 하기 때문이다.
- IPv4를 사용하기 때문에 IPv6의 빈 자리를 맞추기 위해서
자세한 건 밑에서 서술.
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
이해하고 넘어가기 sin_family
sockaddr_in 은 IPv4의 주소정보를 담기 위해 정의된 구조체 하지만 주소체계의 정보를 별도로 sin_family에 저장하고있는 이유는
구조체 sockaddr과 관련이 있다.
구조체 sockaddr은 IPv4의 주소정보만 담기 위해 정의된 구조체가 아니다.
주소정보를 담는 배열 sa_data의 크기가 14바이트인 것만 봐도 알 수 있다. 따라서 구조체 sokaddr에서는 주소체계 정보를 구조체 멤버 sin_family에 저장할 것을 요구하고 있다. 때문에 구조체 sockaddr 과 동일한 바이트 열을 편히 구성학디 위해 정의된 구조체 sokaddr_in에도 주소체계 정보를 담기 위한 멤버가 존재하는 것
네트워크 바이트 순서와 인터넷 주소 변환
CPU는 데이터를 메모리에 저장하는 방식을 두 가지로 나눈다.
CPU가 데이터를 저장하는 방식이 다르면 해석하는 방법도 두 가지로 나뉜다는 뜻.
- 빅 엔디안(Big Endian)
상위 바이트의 값을 작은 번지수에 저장하는 방식 - 리틀 엔디안(Little Endian)
상위 바이트의 값을 큰 번지수에 저장하는 방식
예를 들어 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에서 h는 호스트(host) 바이트 순서를 의미
- htons에서 n은 네트워크(network) 바이트 순서를 의미
- htons에서 s는 자료형 short를 의미
- htonl에서 l은 자료형 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라면 함수 실행 후 바이트 순서가 바뀐 값이 반환된다.
*/
- 본인의 CPU가 Big Endian인 경우에도 네트워크 바이트 순서로 변환하는 것이 좋다. 엔디안에 상관없이 동일하게 동작하는 코드를 작성할 필요가 있기 때문이다.
- 데이터 송수신 기준이 "네트워크 바이트 순서"라고 데이터를 전송하기 전에 직접 데이터를 네트워크 바이트 순서로 변경할 필요는 없다. 이러한 변환의 과정은 자동으로 이루어지며, sockaddr_in 구조체 변수에 데이터를 채울 때 이외에는 바이트 순서를 신경쓰지 않아도 된다.
인터넷 주소의 초기화와 할당
문자열 정보를 네트워크 바이트 순서의 정수로 변환할 필요가 있다.
sockaddr_in 구조체 안에 주소 정보를 저장하기 위해 선언된 멤버는 32비트 정수형으로 정의되어 있다. IP주소 정보의 할당을 위해서 32bit 정수형태로 IP주소를 표현할 수 있어야 한다.
문자열 형태 -> 정수 형태 변환
첫 번째 방법
#include <arpa/inet.h>
in_addr_t inet_addr(const char * string);
/*
성공 시 빅 엔디안으로 변환된 32bit 정수 값, 실패시 INADDR_NONE 반환
*/
- inet_addr 함수는 32bit 정수 형태로 IP주소로 변환할 뿐만 아니라, 유효하지 못한 IP주소에 대한 오류검출도 해준다. 출력 결과를 통해서 네트워크 바이트 순서로 정렬되었음도 확인할 수 있다.
두 번째 방법
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 구조체 변수의 주소 값을 전달하면 자동으로 변환된 값이 구조체 변수에 저장된다.
◆ 문자열 형태 -> 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를 저렇게 초기화하고 사용하면 안 된다.
다른 컴퓨터에서 사용하려면 계속 바꿔줘야 하기 때문이다.
*/
- 서버에서 주소정보를 설정하는 이유
"IP 211.217.168.13, PORT 9190으로 들어오는 데이터는 내게로 다 보내라" - 클라이언트에서 주소정보를 설정하는 이유
"IP 211.217.168.13, PORT 9190 서버로 연결을 해라"
서버는 해당 문장이 bind 함수를 통해서 이루어지고
클라이언트에서는 connect 함수에서 이루어진다.
서버에서는 sockaddr_in 구조체 변수를 하나 선언해서, 이를 서버 소켓이 동작하는 컴퓨터의 IP와 소켓에 부여할 PORT번호로 초기화한 다음에 bind 함수를 호출한다.
클라이언트에서는 sockaddr_in 구조체 변수를 하나 선언해서, 이를 연결한 서버 소켓의 IP와 PORT번호로 초기화한 다음에 connect 함수를 호출한다.
INADDR_ANY
소켓이 동작하는 컴퓨터의 IP주소가 자동으로 할당
◆ 컴퓨터 내에 두 개 이상의 IP주소를 할당 받아서 사용하는 경우
-> 어떤 주소를 통해 데이터가 들어오더라도 PORT 번호만 일치하면 수신함
서버 소켓 생성시 IP주소가 필요한 이유
서버 소켓은 생성시에 자신이 속한 컴퓨터의 IP주소로 초기화가 이루어져야 한다.
즉, 초기화할 IP주소가 뻔하지만 초기화를 하는 이유는 다음과 같다.
하나의 컴퓨터에 둘 이상의 IP주소가 할당될 수 있다.
IP주소는 컴퓨터에 장착된 NIC(랜카드)의 개수만큼 부여가 가능하다.
이러한 경우에는 서버 소켓이라 할지라도 어느 IP주소로 들어오는(어느 NIC으로 들어오는) 데이터를 수신할 지 결정해야 한다. 때문에 서버 소켓이라 할지라도 어느 IP주소로 들어오는 데이터를 수신할지 결정해야 한다.
이러한 이유로 서버 소켓의 초기화 과정에서 IP주소 정보를 요구하는 것이다.
반면 NIC가 하나뿐인 컴퓨터라면 INADDR_ANY를 이용해서 초기화하는 것이 편리하다.
◆ 실행 방식
'스터디 > TCP 와 IP' 카테고리의 다른 글
6. 소켓의 우아한 연결 종료 (0) | 2024.09.03 |
---|---|
5. TCP 기반 서버/ 클라이언트 2 (0) | 2024.09.03 |
4. TCP 기반 서버/ 클라이언트 1 (0) | 2024.09.03 |
2. 소켓의 타입과 프로토콜의 설정 (0) | 2024.09.02 |
1. 네크워크 프로그래밍과 소켓의 이해 (0) | 2024.09.02 |