주소 체계와 데이터 정렬 1


1. Internet Address


IP 주소란, 인터넷상에 존재하는 호스트들을 구분하기 위한 32비트 주소 체계를 의미한다.


일반적으로 점이 찍힌 십진수 표현 방식(Dotted-decimal Notation)을 사용해서 IP 주소를 표현하는데, 점에 의해 구분되는 각각의 십진수 값은 1바이트로  표현된다. 따라서 총 4바이트를 사용하게 된다.


4바이트 IP 주소는 네트워크 주소와 호스트 주소로 나뉘며, 주소의 형태에 따라 A,B,C,D,E 클래스로 분류할 수 있다.



2. Port란 무엇인가?


32비트 IP 주소로는 네트워크상에 존재하는 호스트를 구분하게 되고, 16비트 Port정보로는 호스트 내에서 실행되고 있는 프로그램을 구분하게 된다. 이것은 물리적인 개념의 할당이 아니라, 동일 호스트 내에서의 논리적인 할당일 뿐이다.



Port를 할당 받은 프로세스


Port는 2바이트로 표현되므로 가질 수 있는 값의 범위가 0에서 65535까지다. 그러나 0부터 1023번 까지는 '잘 알려진 Port(well-known ports)' 라고 해서, 예약되어 있는 Port이므로 사용이 제한된다. 사용할 수 없는 것은 아니나 사용되는 용도를 약속해 놓았다는 것이다. 또한 Port는 중복될 수 업승나, TCP 소켓과 UDP소켓은 Port를 서로 공유하지 않으므로 중복되어도 상관없다.


즉 TCP 소켓을 생성할 떄 9000 Port를 사용했다면, 다른 TCP 소켓은 9000 Port를 사용할 수 없지만, UDP소켓은 9000 Port를 사용할 수 있다.


결론적으로 데이터 전송의 최종 목적지는 호스트가 아니라 실행 중에 있는 프로그램이다. 따라서 데이터를 보내기 위해서는 데이터 패킷 내에 IP 주소 정보뿐만 아니라 Port 정보도 함께 포함을 시켜야 한다.



3. 주소 정보의 표현


모든 프로토콜은 자신만의 고유한 주소 포맷이 있다. 예를 들어 IPv4에서는 32비트 주소 체계를 사용하지만, IPv6에서는 128비트 주소 체계를 사용한다. 그러므로 프로토콜에 따라 주소 정보를 나타내닌 데이터 타입이 독립적으로 존재한다.


1. IPv4의 주소 체계를 나타내는 구조체

struct sockaddr_in {
	sa_family_t	sin_family;	/* 주소 체계(address family) */
	uint16_t	sin_port;	/* 16비트 TCP / UDP Port */
	struct in_addr	sin_addr;	/* 32비트 IPv4 주소 */
	char		sin_zero[8];	/* 사용되지 않음 */
};


struct in_addr {
	uint32_t	s_addr;	/* 32비트 IPv4 인터넷 주소 */
};

이러한 데이터 타입들은 POSIX에서 그 근거를 찾을 수 있다. POSIX(Portable Operation System Interface)란 유닉스 계열의 운영 체제를 위해 표준화 해 놓은 인터페이스(API)다.



Data type 

Description 

Header 

 int8_t

 uint8_t

 int16_t

 uint16_t

 int32_t

 uint32_t

 signed 8-bit int

 unsigned 8-bit int (unsigend 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> 



2. sockaddr_in 구조체 정보


■ sin_family : 사용되는 주소 체계에 대한 정보를 대입한다.


주소체계(Address Family) 

정의 

 AF_INET 

 IPv4 인터넷 프로토콜

 AF_INET6

 IPv6 인터넷 프로토콜 
 AF_LOCAL Local 통신을 위한 UNIX 프로토콜 


■ sin_port : 16비트 Port 정보를 대입해 준다. 네트워크 바이트 순서로 대입해야 한다.


■ sin_addr : 32비트 IP 주소 정보를 대입해 준다. 네트워크 바이트 순서로 대입해야 한다.


■ sin_zero : 특별한 의미 없이 단순한 채워주기 위한 목적으로 사용되는 구조체 멤버이다.



4. 네트워크 바이트 순서


바이트 순서라는 것은 시스템이 내부적으로 데이터를 표현하는 방법을 의미한다. 모든 시스템의 내부적인 데이터 표현방식이 같은 것은 아니다.


크게 두 가지 방법으로 나뉘는데, 하나는 Big-Endian 방식이고 또 하나는 Little-Endian 방식이다. 이 두가지 방법을 이용하여 0x12345678이라는 32비트 값을 표현해 보자.


1. 바이트 순서(Byte Order)


■ Big-Endian 표현 방식 : 0x12345678

상위 바이트의 값이 메모리상에 먼저 표시되는 방법이다.


■ Little-Endian 표현 방식 : 0x78563412

하위 바이트의 값이 메모리상에 먼저 표시되는 방법이다.


시스템이 내부적으로 데이터를 처리하는데 있어서 Big-Endian 방식을 쓰느냐, Little-Endian 방식을 쓰느냐는 시스템의 CPU에 따라 달라진다. 이를 '호스트 바이트 순서(Host Byte Order)'라고 하는데 문제는 호스트 바이트 순서가 일정치 않다는 것이다. Motorola 68000계열이 주소 Big-Endian 방식을 쓰고, 가장 많이 사용되는 Intel x86 계열은 Little-Endian 방식을 쓰고 있다. 따라서 만약에 서로 다른 CPU를 장착하고 있는 호스트들이 데이터를 주고 받을 경우 문제가 발생할 수 있다.


이러한 문제점 떄문에 네트워크를 통해 데이터를 전송할 떄는 통일된 방식을 이용해 데이터를 전송하기로 약속을 하였는데, 이것이 바로 '네트워크 바이트 순서(Network Byte Order)'이다. 네트워크 바이트 순서는 Big-Endian 방식만을 사용하기로 약속되어 있다.


2. 바이트 순서 변환(Endian Conversions)


  'h' : host byte order

 'n' : network byte order 

  's' : short (16bit)

 'l' : long (32bit) 


● unsigned short htons(unsigned short);

● unsigned short ntohs(unsigned short);

● unsigned long htonl(unsigned long);

● unsigned long ntohl(unsigned long);




posted by deviAk

소켓의 생성과 프로토콜의 설정


1. 프로토콜의 정의


프로토콜이란 '컴퓨터 상호간의 대화에 필요한 통신 규약'을 의미 한다. 그러나 네트워크 프로그래밍에서 프로토콜이란 용어는 조금 더 다양하게 사용된다. 



2. 소켓의 생성


소켓을 생성하기 위해서 리눅스 윈도우즈 공통으로 socket이란 함수를 사용하게 된다. socket 함수는 호출 시 시스템 내부적으로 소켓을 생성하고 그 소켓을 조작하기 위해 필요한 파일 디스크립터를 리턴하는 함수이다.


시스템 내부적으로 소켓을 생성한다는 의미는 호스트가 통신을 하기 위해 필요한 리소스를 할당하는 것을 의미한다.

#include <sys/types.h>
#include <sys/socket.h>

// 성공 시 파일 디스크립터, 실패 시 -1 리턴
int socket(int domain, int type, int protocol);

- domain : 생성할 소켓이 통신 하기 위해ㅔ 사용할 프로토콜 체계(Protocol Family)를 설정한다.

- type : 소켓이 데이터를 전송하는데 있어서, 사용하게 되는 전송 타입을 설정해 준다.

- protocol : 두 호스트간에 통신을 하는데 있어서 특정 프로토콜을 지정 하기위해 사용된다.



3. 프로토콜 체계(Protocol Family)


sys/socket.h에 선언되어 있는 프로토콜 체계

프로토콜 체계(Protocol Family)

정의

 PF_INET

 IPv4 인터넷 프로토콜 

 PF_INET6

 IPv6 인터넷 프로토콜

 PF_LOCAL

 Local 통신을 위한 UNIX 프로토콜

 PF_PACKET

 Low Level socket을 위한 인터페이스

 PF_IPX

 IPX 노벨 프로토콜



4. 소켓의 타입


socket 함수의 두 번쨰 인자로 소켓의 타입을 설정해야 한다. 여기서 의미하는 타입이란 데이터 전송 타입을 말하는 것이다. 하나의 프로토콜 체계 안에서도 데이터를 전송하는 방법이 둘 이상 존재할 수 있다.


1. SOCK_STREAM

SOCK_STREAM 타입으로 설정하면 소켓은 연결 지향형 소켓이 된다. 


- 에러나 데이터의 손실 없이 무사히 전달된다.

 독립된 전송 라인을 통하여 데이터를 전달하기 때문에 라인상에 문제만 없다면, 데이터가 반드시 전달된다는 것을 보장받을 수 있다. 또한 하나의 라인이 존재한다는 것은 반디스 호스트 대 호스트의 연결은 1:1이어야 한다는 것을 의미한다.


- 전송하는 순서대로 데이터가 전달된다.

 전송 라인이 하나이기 때문에 뒤에 보낸 데이터가 이전에 보낸 데이터보다 일찍 도착할 수는 없을 것이다. 따라서 데이터를 전송하는 호스트는 순서대로 데이터가 전송된다는 믿음을 가질 수 있다.


- 전송되는 데이터의 경계(Boundary)가 존재하지 않는다.

 호스트 대 호스트가 연결 지향 소켓을 생성해서 통신을 하는 경우, 두 번의 write 함수 호출을 통해서 데이터를 전송했다 하더라도, 수신측 호스트의 버퍼가 넉넉하다면 한번의 read 함수 호출을 통해서 모든 데이터를 수신할 수 있다. 또한 반대로 한번의 write 함수 호출을 통해서 데이터가 전송되었다 하더라도, 서너 번의 read 함수 호출을 통해서 데이터를 조금씩 나누어 수신할 수도 있다.

 이러한 특성을 두고, 전송되는 데이터의 경계가 존재하지 않는다고 표현한다.


SOCK_STREAM의 특징은 "신뢰성 있는 순차적인 바이트 기반의 연결 지향 전송 타입"이라 말할 수 있다.



2. SOCK_DGRAM

SOCK_DGRAM 타입으로 설정하면 소켓은 비연결 지향형 소켓이 된다. 


- 전송되는 순서에 상관없이 가장 빠른 전송을 지향한다.

 출발한 순서가 크게 차이나지 않는 한, 어떤 데이터가 먼저 도착할 지에 대한 예측은 불가능하다. 오로지 빠른 시간 내에 전송 하려고 최선의 노력을 다한다는 믿은만 있을 뿐이다.


- 전송되는 데이터는 손실될 수도 있고 에러가 발생할 수도 있다.

 데이터가 빠르게 전달되지만 경우에 따라서는 손상되거나 잃어버릴 수도 있다.


- 전송되는 데이터의 경계(Boundary)가 존재한다.

 데이터를 전송하는 호스트가 세 번의 함수 호출을 통해서 데이터를 전송했다면, 수신하는 호스트도 반드시 세 번의 함수 호출을 거쳐야 데이터를 완전히 수신할 수 있게 된다. 데이터의 경계가 없었던 SOCK_STREAM타입과는 대조를 이룬다.


- 한번에 전송되는 데이터의 크기는 제한된다.

비연결 지향형 소켓을 기반으로 데이터를 전송할 경우 호스트가 한번에 전송하는 데이터의 크기는 제한될 수 밖에 없다. 따라서 그 크기가 크다면 적절히 데이터를 나누어야 한다.


비 연결 지향형 소켓의 경우 빠른 전송을 위해 최선을 다하지만, 경우에 따라서는 데이터의 손실이 있을 수도 있으며, 전송되는 데이터의 크기가 제한되어 있기 때문에 덩치가 큰 데이터를 전송할 경우 나누어서 보내야 한다.



5. 프로토콜의 선택


socket 함수의 세 번째 전달되는 인자는 호스트 대 호스트가 사용할 프로토콜을 설정하기 위해 사용된다.


프로토콜 체계가 PF_INET인 경우 다음과 같은 값이 올 수 있다.


- IPPROTO_TCP : TCP를 기반으로 하는 소켓을 생성

- IPPROTO_UDP : UDP를 기반으로 하는 소켓을 생성


TCP 소켓이란 인터넷을 기반으로 하는 연결 지향형 소켓을 의미한다. 또한 UDP 소켓이란 인터넷을 기반으로 하는 비연결 지향형 소켓을 의미한다. 



6. 윈도우즈 기반으로 구현하기


socket 함수는 윈도우즈 기반으로 변경하더라도 큰 차이점은 보이지 않는다.

#include <winsock2.h>

// 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET 리턴
SOCKET socket(int af, int type, int protocol);




posted by deviAk

네트워크 프로그래밍과 소켓의 이해


1. 네트워크 프로그래밍의 이해


네트워크 프로그래밍이란?

멀리 떨어져 있는 호스트들이 서로 데이터를 주고 받을 수 있도록 프로그램을 구현하는 것이다. 파일과 달리 데이터를 주고 받을 대상이 멀리 떨어져 있기 때문에 소프트웨어 차원에서 호스트들간에 연결을 해주는 장치가 필요하다. 이러한 기능을 해주는 장치를 소켓(socket)이라 한다. 일반적으로 소켓 프로그래밍이라는 용어와 네트워크 프로그래밍이라는 용어는 같은 의미로 사용되고 있다.



2. 소켓 이해하기


1. 서버 소켓 구현의 이해

소켓이란 멀리 떨어져 있는 두개의 호스트(host)를 연결시켜 주는 매개체 역할을 한다. 네트워크 프로그래밍에서 소켓이 필요한 이유는 바로 그것이다.


다음은 소켓을 생성하는 함수 선언이다.

#include <sys/types.h>
#include <sys/socket.h>

// 성공시 파일 디스크립터, 실패 시 -1 리턴
int socket(int domain, int type, int protocol);


전화기에 전화번호를 할당하는 것처럼, 소켓에도 전화번호에 해당하는 소켓의  IP 주소를 할당해야 한다.

다음은 소켓에 주소를 할당하는 함수의 선언이다.

#include <sys/socket.h>

// 성공시 0, 실패 시 -1 리턴
int bind(int sockfd, struct  sockaddr *myaddr, int addrlen);


소켓이 연결 요청이 가능한 상태가 되어야 한다.

다음 함수는 소켓을 연결 요청이 가능한 상태가 되게 한다.

#include <sys/socket.h>

// 성공시 0, 실패 시 -1 리턴
int  listen(int sockfd, int backlog);


누군가 데이터를 주고 받기 위해 연결 요청을 해 오면, 그 요청을 수락할 수 있어야 한다.

다음은 요청을 수락하는 함수이다.

#include <sys/socket.h>

// 성공시 파일 디스크립터, 실패 시 -1 리턴
int accept(int sockfd, struct sockaddr *addr, int *addrlen);



3. 윈도우즈 기반으로 구현하기


1. 윈도우즈 소켓을 위한 헤더와 라이브러리 설정하기

윈속2를 기반으로 프로그램을 개발하기 위해서는 반드시 winsock2.h 헤더를 포함해야 한다. 또한 winsock2.h 헤더를 포함하기 위해서는 ws2_32.lib 라이브러리를 링크시켜야 한다.



2. 윈속 초기화 하기

윈속 프로그래밍을 할 때 반디스 WSAStartup 함수를 호출해 줘야 한다. 이 함수를 호출하는 목적은 프로그램에서 요구하는 윈속의 버전을 알려줘, 해당 버전의 윈속 사용을 위한 라이브러리 초기화 작업을 진행하기 위한 것이다.

#include <winsock2.h>

// 성공 시 0, 실패 시 0이 아닌 에러 코드 리턴
int WSAStartup(
	WORD wVersionRequested,
	LPWSADATA lpWSAData
);


- wVersionRequested : 프로그램에서 요구하는 윈속의 최상위 버전을 알려주기 위해 사용된다. WORD는 16비트 unsigned int를 의미하며, 상위 8비트는 부 버전, 하위 8비트는 주 버전을 표시해 준다. MAKEWORD 함수(매크로 함수)가 제공되며 이 함수를 사용하면 원하는 WORD값을 쉽게 만들 수 있다.

- lpWSAData : WSADATA타입 변수의 포인터를 인자로 전달한다. 함수 호출이 끝나면 WSADATA 변수에는 로딩한 DLL에 대한 정보가 채워진다.

#include <winsock2.h>

WORD  MAKEWORD(
	BYTE bLow,
	BYTE bHigh
);

MAKEWORD는 매크로 함수로 원하는 WORD값을 만들어 준다. 



초기화를 해 주었다면, 종료 시에는 그에 따른 적절한 처리도 해 줘야 한다. WSACleanup 함수 호출을 통해서 할당 받은 리소스를 해제하는 작업을 해야 한다.

#include <winsock2.h>

// 성공 시 0, 실패 시 SOCKET_ERROR 리턴
int WSACleanup(void);



3. 윈속 기반의 소켓 관련 함수


■ 소켓의 생성

#include <winsock2.h>

// 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET 리턴
SOCKET socket(int af, int type, int protocol);


■ 주소와 Port 할당

#include <winsock2.h>

// 성공 시 0, 실패 시 SOCKET_ERROR 리턴
int bind(SOCKET s, const sturct sockaddr FAR *name, int namelen);


■ '연결 요청 대기 상태'로의 진입

#include <winsock2.h>

// 성공 시 0, 실패 시 SOCKET_ERROR 리턴
int listen(SOCKET s, int backlog);


■ 연결 수락

#include <winsock2.h>

// 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET
SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);


■ 연결 요청

#include <winsock2.h>

// 성공 시 0, 실패 시 SOCKET_ERROR 리턴
int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);



4. 윈속 기반의 데이터 입출력 함수

#include <winsock2.h>

// 성공 시 전송한 바이트 수, 실패 시 SOCKET_ERROR 리턴
int send(SOCKET s, const char FAR *buf, int len, int flags);

- s : 데이터를 전송할 호스트에 연결된 소켓의 핸들을 인자로 전달한다.

- buf : 전송할 데이터를 저장하고 있는 버퍼를 가르키는 포인터이다.

- len : 전송할 바이트 수를 인자로 전달한다.

- flags : 함수 호출 시, 여러 가지 옵션을 설정하기 위해서 사용된다.



#include <winsock2.h>

// 성공 시 수신한 바이트 수(단 EOF 전송시 0), 실패 시 SOCKET_ERROR 리턴
int recv(SOCKET s, char FAR *buf, int len, int flags);

- s : 데이터를 수신할 영역을 나타내는 소켓의 핸들이다.

- buf : 수신할 데이터를 저장할 버퍼를 가르키는 포인터이다.

- len : 수신할 최대 바이트 수이다.

- flags : 함수 호출 시, 여러가지 옵션을 설정하기 위해서 사용된다.










posted by deviAk