2014년 12월 8일 월요일

TCP/IP 소켓 프로그래밍 3강

이번 강은 주소체계와 데이터 정렬를 알아볼 것이다.
소켓에는 IP주소와 PORT번호가 할당되어야 한다.
먼저 IP주소의 체계가 뭔지 알아야 할당을 할 것아닌가? (아님 말고 ㅡㅡ;)

지금 우리가 많이 쓰는 주소는 IPv4 다.
이 주소는 4바이트 주소 체계다.
체세대 주소 체계라고 불리는 IPv6는 16바이트 주소체계다.
4바이트가 뭐냐고???
1바이트 = 8비트 다. 즉, 2의 8승개의 숫자를 표현할 수 있다.
2의 8승 = 256이다.
즉 0~255 까지 표현 가능한 숫자를 4개를 쓴다는 것.
보통 IP 주소를 192.168.0.1 이런식으로 쓰지 않는가??

주소 형태에 따라서 a~e 클래스로 분류를 한다.
이건 아래 표를 보고 이해하면 된다(위키에서 퍼왔다)

여기서 E 클래서는 일반적이 아닌 예약되어 있는 전용 IP라서 쓰지 못한다고 한다.
어디에 예약 되어 있는지는 모름...;;
자~ 일단 IP주소의 체계는 알았으니

PORT를 알아보자. 이놈은 뭐하는 놈일까??
IP가 집 주소라면, PORT는 각 방에 있는 문이라고 생각하면 된다.
누구는 화장실 문으로, 도둑은 창문으로, 택배 아저씨는 현관문으로, 음식은 냉장고 문으로
들어오고 나가지 않는가?
이렇게 각자의 필요에 의해서 문을 만들어 쓰는 것이 PORT로 생각하면 편하다.

우리는 보통 동시에 여러 인터넷을 사용한다.
토렌트를 다운 받으면서, 인터넷 서핑을 하며, 음악 스트리밍을 듣는다.
이걸 한개의 소켓으로 해결하려고 하면 인터넷이 엄청 빠르지 않는한 분명 충돌이 나고 난리가 날 것이다.
이걸 아예 나눠서 전용 PORT를 만들어서 따로따로 받으면 충돌도 안나고 동시에 여러 작업이 수월하게 될 것이다.

PORT번호는 16비트로 표현되기에 범위가 0~65536 까지다.
이 범위에서 얼마든지 내가 할당해서 쓰면 된다.
그리고 주의할 점은 포트는 중복해서 할당하면 충돌이 일어나기에 따로따로 할당해야 한다.
0~1023번까지는 os에서 주로 쓰는 번호가 있기에
개인적으로 포트를 열어서 쓰려면 그 이상부터 할당해서 쓰는 것이 좋다.

또한 TCP 소켓을 생성할 때 20000 번을 할당했다면 다른 TCP 소켓에서는 20000번은 피해서 할당해야 하지만, UDP 소켓은 할당 할 수 있다.
체계 자체가 다르기 때문이다.
하지만 그것도 문제가 생길 수도 있으니 걍 포트는 다 피해서 할당하자 ㅡ.ㅡ

IP와 PORT의 주소체계를 간단히 알아봤으니 이걸 할당하는 구조체를 살펴보자

struct sockaddr_in
{
     sa_family_t             sin_family;    //주소체계
     uint16_t                 sin_port;       //16비트 TCP/UDP PORT번호
     struct in_addr         sin_addr;      //32비트 IP 주소
     char                      sin_zero[8];  //사용되지 않음
}

이번에는 바이트 순서와 네트워크 바이트 순서를 알아보자.
CPU가 데이터를 메모리에 저장하는 방식은 크게 두가지가 있다.

빅 엔디안 - 상위 바이트 값을 작은 번지수에 저장하는 방식
리틀 엔디안 - 상위 바이트 값을 큰 번지수에 저장하는 방식

라고 쓰여있는데 이게 뭔 x소리야~! 할 것 같아서 예를 들어본다

12345678를 저장하는데
12 34 56 78 로 저장하는 방식은 빅 엔디안 바이트 표현

78 56 34 12 로 저장하는 방식은 리틀 엔디안 바이트 표현이다.

어떤 미친 xx들이 리틀 엔디안으로 저장하냐고 버럭했는데 인텔 계열 CPU가 저리 저장한다고 했다....;;(내 컴도 인텔...)

이래서 네트워크 상에 데이터를 주고 받을 때 통일된 방법이 필요했다
그래서 "빅 엔디안" 방식으로 통일하기로 합의를 했다.(내가 봐도 당연하다)

그래서 네트워크로 데이터를 보내려면 빅 엔디안으로 변환을 해서 보내야 한다
(인텔이 리틀 엔디안을 포기해!!!!!)

하여간 xxx들 때문에 쓸데없이 변환하는 것도 배워야 한다 ㅡㅡ
바이트 순서의 변환을 돕는 함수를 보자.

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

이게 뭔가 하지만 나름 패턴이 있는 이름이다.
htons는 h는 호스트 바이트 순서, n는 네트워크 바이트 순서다
이걸 해석하면(?) 데이터를 호스트(h) to 네트워크(n)를 short형(s)으로 변환해라!

위에는 말로 쓰여있으니 코드로 정리해서 보자
인터넷 주소의 초기화 코드

struct sockaddr_in addr;
char *serv_ip = "192.168.0.1";   //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번호 초기화

하지만 맨날 이렇게 초기화 하면 빡칠 수가(?) 있기 때문에 아래와 같이 바꿀 수 있다.

struct sockaddr_in addr;
char *serv_port = "9190";          //PORT번호 문자열 선언
memset(&addr, 0, sizeof(addr)); //구조체 변수 addr의 모든 멤버 0으로 초기화
addr.sin_family = AF_INET;        //주소체계 지정
addr.sin_addr.s_addr = inet_addr(INADDR_ANY); //문자열 기반의 IP주소 초기화
addr.sin_port = htons(atoi(serv_port));      //문자열 기반의 PORT번호 초기화

파란부분처럼 서버의 IP주소를 상수화해서 초기화 하면 편하다.

역시 이것도 3강 소스를 다운 받아서 직접 해보자.
소스 코드 다운 받기 

4강은 TCP 기반 서버와 클라이언트를 해보자.

댓글 없음:

댓글 쓰기