이론 : IP주소와 포트번호
네트워크 이론과 겹치는 내용이므로 짧게 다루고 넘어감.
<IP주소 복습>
* IPv4 : 4바이트 주소체계
* IPv6 : 16바이트 주소계체
* 이 책에서는 IPv4를 위주로 설명하고 있다.
IP는 네트워크 주소와 호스트 주소로 나뉠 수 있다.
<포트번호>
* 네트워크를 사용하는 어플리케이션들을 구분하기 위한 장치
ex. 동영상을 위한 포트와 인터넷을 위한 포트를 구분
* 하나의 운영체제 내에서 포트번호는 1개의 소켓에게만 부여.
* 0 ~ 1023번 : Well-known port
* 1024 ~ 65535번 : 비어있는 포트
<데이터가 들어오는 과정>
1. 전선
2. NIC (네트워크 인터페이스 카드) => IP주소 사용
3. 운영체제에 의해 포트로 분배 => 포트번호 사용
주소정보의 표현 : 구조체
IP주소와 포트번호를 소켓에 넣는 방법은?
주소정보 저장용 구조체
이를 위해 만들어진 구조체가 바로 sockaddr_in
struct sockaddr_in
{
sa_family_t sin_family; // 주소체계 (signal family)
uint16_t sin_port; // 16비트 포트번호 (signal port)
struct in_addr sin_addr; // 32비트 ip주소 (signal address)
char sin_zero[8]; // 패딩 (현재는 사용하지 않음)
};
sin_family : 주소체계를 의미.
-
쓸 수 있는 대표적인 값은 다음과 같다.
AF_INET = IPv4, AF_INET6 = IPv6, AF_LOCAL = 로컬 통신
sin_port : 포트번호를 저장. 단, 네트워크 바이트 순서대로 저장해야 한다.
sin_addr : ip주소를 저장. 단, 네트워크 바이트 순서대로 저장해야 한다.
- 이를 이루는 구조체
in_addr는 다음과 같다.
struct in_addr
{
// 32비트 인터넷 주소.
in_addr_t s_addr;
}
sin_zero : 예전에는 sin_zero를 쓰지 않으면 메모리 정렬 문제 발생
- 구조체의 크기를 특정한 배수로 맞춰야지만 해당 문제를 해결.
- 해당 목표를 달성할 때까지 0을 채워 넣으면 된다.
- 현재는 이러한 문제가 더 이상 없기 때문에 해결.
네트워크 바이트 순서와 인터넷 주소 변환
바이트 저장순서
CPU가 바이트를 저장하는 순서는 다음과 같다.
- 빅 엔디안 : 상위 바이트의 값을 작은 번지수로
- 리틀 엔디안 : 상위 바이트의 값을 큰 번지수로
이를 호스트 바이트 순서라고 한다. 인텔 CPU는 리틀 엔디안 방식을 따른다.
그런데, 소켓 프로그래밍을 할 때는 바이트 저장방식이 2가지인 것이 문제가 된다.
ip주소나 포트번호 역시 바이트인데, 통신하는 컴퓨터가 다른 호스트 바이트 순서를 가진다면?
그래서, 네트워크 바이트 순서라는 것을 만들었고, 이는 다음과 같다.
네트워크 바이트 순서는 빅 엔디언 방식.
이를 위한 함수는 다음과 같다.
// h : 호스트(host) 바이트 순서
// n : 네트워크(network() 바이트 순서
// s : short
// l : long
unsigned short htons(unsigned short); // host 방식을 network 방식으로, short
unsigned short ntohs(unsigned short); // network 방식을 host 방식으로, short
unsigned long htonl(unsigned long); // host 방식을 network 방식으로, long
unsigned long ntohl(unsigned long); // network 방식을 host 방식으로, long
인터넷 주소 변환
문자열 → 네트워크 바이트 순서의 정수로 변환하는 함수들을 알아보자.
#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
int inet_aton(const char *string, struct in_addr *addr);
- 문자열을 ip주소 그 자체로 바꾸고 싶다면
inet_addr - ip주소와 포트번호가 같이 담긴 구조체를 변경 시
inet_aton- 리턴값은 0과 1로, 실패/성공을 의미한다.
- 둘 다 문자열을 32비트 정수형태로 바꿔주고, 네트워크 바이트 순서를 맞춰주며, 잘못된 형태의 문자열을 검출할 수 있다.
반대로, 네트워크 바이트 순서의 정수를 문자열 정보로 바꾸는 함수도 있다.
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
- ip주소를 문자열로 바꾸는 역할. 성공 시 변환된 문자열의 주소값, 실패 시 -1
- 사용자가 동적 할당을 안 해도 됨. 따라서 반환값을 미리 복사하는 것이 필요.
인터넷 주소의 초기화와 할당
정리 : 인터넷 주소 초기화
struct sockaddr_in serv_addr;
// addr의 모든 멤버를 0으로 초기화
memset(&serv_addr, 0, sizeof(serv_addr));
// sin_family = 주소체계 지정
serv_addr.sin_family = AF_INET;
// s_addr = 문자열 기반의 ip주소 초기화
serv_addr.sin_addr.s_addr = inet_addr("211.217.168.13");
// sin_port = 문자열 기반의 포트번호 초기화
serv_addr.sin_port = htons(atoi("9910"));
INADDR_ANY
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
서버 소켓의 생성 과정에서 매번 서버의 IP주소를 입력하기 귀찮을수도 있다.
이때, INADDR_ANY라는 상수를 이용하면 서버의 IP주소를 자동으로 할당할 수 있다.
인터넷 주소 할당
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
sockfd: 소켓 = 파일 디스크립터myaddr: 주소정보 구조체의 주소값addrlen:myaddr의 길이