반응형

4-1 TCP/UDP에 대한 이해

TCP/IP 프로토콜 스택

응용프로그램 계층      : 응용프로그램     응용프로그램

전송(Transport)  계층 :       TCP                UDP

네트워크 (Network)계층:                  IP

데이터링크 계층 :                          LINK

물리 계층 


데이터링크 계층

LAN, WAN, MAN과 같은 네트워크 표준과 관련된 프로토콜의 정의한다.


IP 계층

어떻게 길을 찾아 갈 것인가?

라우터끼리는 (도로사정)이 어떤지 서로 통신하여 패킷을 보낼 최적의 길을 찾는다.

신뢰할 수 없는 프로토콜(데이터가 손실 될 수 있고) 경로가 일정치 않으며 패킷을 보낸 순서도 보장하지 못한다. 비연결 지향 프로토콜이다.


TCP/UDP

(IP 계층을 기반으로 하여 ) 데이터를 어떻게 전송할 것인가?

TCP/UDP(2가지 프로토콜) 중에 원하는 프로토콜을 선택한다.


TCP와 IP의 관계

IP를 기반으로 길을 찾고 TCP를 통해 연결 지향 및 신뢰성 있는 통신을 제공한다.

호스트대 호스트가 어떻게 데이터를 주고 받을 것인지 약속.

IP(편지에 비유) -> 패킷(편지)을 잃어버림


TCP의 역할 

A 호스트가 B 호스트에게 패킷을 하나 전송한다.

1. B가 잘 받았을 경우 : 응답용 패킷을 A에게 전송한다.

2. B가 잘 못 받았을 경우 : A는 B가 데이터를 수신하지 못했다고 간주하고 임의의 시간 후 재전송.


4-2. TCP 기반 서버의 구현

socket - 소켓 생성

bind - 주소할당

listen - 연결 요청 대기 상태 (친구에게 전화가 올 수 있는 상태)

accept - 연결 허용(수화기를 든다.)

read & write - 데이터 송수신

close - 연결 종료


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

1. listen 함수는 전달되는 인자의 소켓을 '서버 소켓'이 되게 한다.

2. listen 함수는 backlog의 수(대기실의 크기)만큼 '연결 요청 대기 큐'를 생성 한다.

3. 성공하면 '연결 요청 대기 상태'가 된다.

#include<sys/type.h>

int listen(int s, int backlog);

#include<winsock2.h>

int listen(SOCKET s, int backlog);

 - socket : 서버소켓으로 만들 핸들 혹은 파일 디스크립터.

 - backlog : 대기 큐(실)의 크기, 클라이언트 수.


서버의 역할과 연결요청 대기상태

서버 소켓은 일종의 '문지기'이다. (클라이언트의 연결 요청을 감지하고 받는다.)

클라이언트 - 서버로의 연결요청 - 서버 소켓 -> 대기실

※ 여러 사람의 요청을 받게 하기 위해 '큐(대기실)'를 만든다.


연결요청 수락하기

연결요청 대기 큐에 존재하는 클라이언트의 연결 요청 수락.


#include<sys/type.h>

#include<sys/socket.h>

int accept(int s, struct sockaddr *addr, int *addrlen);

#include<winsock2.h>

SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

리턴값 : 클라이언트와 연결하기 위한 (밑의 그림의)New 소켓

addr : 클라이언트의 주소 정보가 채워진다.

addrlen : 정보구조체의 크기 정보가 채워진다.

 

listen ~ accept 과정

※ New 소켓은 자동적으로 만들어지며 이 New 소켓으로 클라이언트와 데이터 송수신이 가능하다.


4-3 TCP 기반 클라이언트의 구현


#include<sys/types.h>

#include<sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd : 연결 요청을 할 소켓

serv_addr : 주소정보

addrlen : 구조체 크기


※ 소켓은 연결하기 전에 미리 운영체제(커널)에 의해 임의로 할당된다.


4-4 서버/클라이언트 함수 호출 관계




※ listen()에서 대기 큐를 만들고 연결 요청을 받을 수 있는 서버 소켓을 만든다.

※ connect 함수는 서버의 IP, 포트를 설정해 주면 자동으로 클라이언트 컴퓨터 내의 임의의 포트에 운영체제가 통신하기 위한 소켓을 생성한다.

※ accept() 함수는 연결 요청 대기 큐가 비어있을 때까지 혹은 클라이언트가 연결할 때까지 block되어 있다.

=> 리턴 되면서 연결된다.


4-5 Iterative 서버의 구현

Iterative 서버 : 반복해서 클라이언트의 요청을 처리한다.

여러 클라이언트가 서버로 연결 요청을 한다. -> 연결 요청 대기 큐로 들어간다. -> 서버가 accept를 호출할 때마다 대기 큐에서 하나의 소켓을 꺼낸다. -> 그 소켓과 통신을 하고 끝나면 반복해서(Iterative) accept를 호출한다. 


Iterative Server Template

※ 리턴 클라이언트 소켓은 하나다.


4-6 에코(echo) 서버/클라이언트의 구현

concurrent 서버(서버 하나와 클라이언트 여러 개 관계)

※ 버퍼 : 여기서는 프로그램 상의 버퍼. 문자열을 저장할 수 있는 배열.

※ 클라이언트에서 close(소켓핸들);을 호출하면 서버의 read함수의 리턴 값은 0이 된다.

반응형
반응형

3-1 Internet Address

IP(Internet Address) 

인터넷에 존재하는 호스트들을 구분하기 위한 32비트 주소 체계

점이 찍힌 십진수 표현 방식(Dotted-decimal Notation) : 211.217.10.9


클래스

Class A : 0.0.0.0 ~ 127.255.255.255, 1바이트는 네트워크 ID, 3바이트는 호스트 ID

Class B : 128.0.0.0 ~ 191.255.255.255, 2바이트는 네트워크 ID, 2바이트는 호스트 ID

Class C : 192.0.0.0 ~ 223.255.255.255, 3바이트는 네트워크 ID, 1바이트는 호스트 ID

Class D : 멀티캐스트 주소. 224.0.0.0 ~ 239.255.255.255

Class E : 예약됨. 240.0.0.0 ~ 255.255.255.255


IP 주소 : 네트워크 주소(네트워크를 구분지음) + 호스트 주소(호스트를 구분지음)

subnet mask에 따라서 네트워크 주소(ID)와 호스트 주소(ID)로 나누어진다.


※ 라우터는 네트워크 ID(주소)만 참조한다.

※ 루프백 주소 : 127.x.x.x, 네트워크 상으로 패킷을 전송하지 않고 자기자신에게 돌려준다.


3-2 Port란 무엇인가?

Port

호스트 내에서 실행되고 있는 프로세스를 구분 짓기 위한 16비트의 논리적인 값. (소켓에 할당)

논리적인 값 : 소프트웨어 적으로 구현. 

Well-known ports : 0~1023


3-3 주소 정보의 표현

IPV4의 주소 체계를 나타내는 구조체

※ 하나의 프로토콜 내에 2개 이상 프로토콜이 있을 것을 대비해서 만든 구조체. ()는 윈도우즈용.

※ 모든 데이터는 네트워크 바이트 순서로 저장해야 한다.

struct sockaddr_in

{

sa_family_t(short) sin_family; // 주소 체계(address_family) - AF_INET

uint16_t(unsigned short) sin_port; // 16비트 TCP 혹은 UDP 포트

struct in_addr sin_addr; // 32비트 IPv4 주소

char  sin_zero[8]; // 사용되지 않음. padding 용도

};


struct in_addr

{

uint32_t(unsigned long) s_addr; // 32비트 IPv4 인터넷 주소

};



3-4 네트워크 바이트 순서

0x12345678을 Big-Endian으로는 메모리 순서대로 0번지 0x12, 1번지 0x34, 2번지 0x56, 3번지 0x78

0x12345678을 Little-Endian으로는 메모리 순서대로 0번지 0x78, 1번지 0x56, 2번지 0x34, 3번지 0x12

형태로 저장.


호스트 바이트 순서 

어떤 시스템(motorola, sun사 기계)은 Big-Endian, 어떤 시스템(intel사)은 Little-Endian을 사용하므로 일정하지 않다.

※ 데이터 표현 방식이 다르므로 다른 플랫폼끼리 문제가 발생한다.


네트워크 바이트 순서

Big-Endian 방식을 적용하기로 하였다.


바이트 순서 변환 함수

※ 어느 시스템에서 돌아갈 지 모르기 때문에 반드시 사용해야 한다.

unsigned short htons(unsigned short); // 호스트 -> 네트워크 short

unsigned short ntohs(unsigned short); // 네트워크 -> 호스트 short

unsigned long htonl(unsigned long); // 호스트 -> 네트워크 long

unsigned long ntohl(unsigned long); // 네트워크 -> 호스트 long


'h' : host byte order

'n' : network byte order

's' : short(16비트)

'l' : long(32비트)


3-5 인터넷 주소 조작하기

1. 점이 찍힌 십진수 방식(Dotted-Decimal Notation)을 Big-Endian 32비트 정수형 데이터로 바꿔주는 함수(네트워크)

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

unsigned long inet_addr(const char *string);

리턴 : 성공시 32비트 big-endian값, 실패시 INADDR_NONE


#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

int inet_aton(const char* string, struct in_addr *addr);
리턴 : 성공시 true, 실패시 false
※ 바로 struct in_addr에 값을 제공할 수 있다.


2. Big-Endian 32비트 정수형 데이터를 점이 찍힌 십진수 방식로 바꿔주는 함수(네트워크)

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

char* inet_ntoa(struct in_addr addr);

※ 따로 inet_ntoa 내부적으로 static 배열을 통해 주소에 대한 문자열이 존재한다.


3-6 인터넷 주소 초기화

struct sockaddr_in addr;

char *serv_ip = "...";

char *serv_port = "...";

memset(&addr, 0, sizeof(addr_len)); // 주소에 해당하는 구조체 값을 0으로 초기화. 좋은 습관.

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = inet_addr(serv_ip);

addr.sin_port = htons(atoi(serv_port));


INADDR_ANY?

addr.sin_addr.s_addr = htons(INADDR_ANY);

// 서버의 IP가 뭐더라? 답은 INADDR_ANY : 내 시스템의 IP 주소를 찾아 알아서 할당.


3-7 주소 정보 할당하기

리눅스

#include<sys/types.h>

#include<sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

윈도우즈

#include<winsock2.h>

int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);


sockfd/s : 주소를 할당하고자 하는 소켓의 파일 디스크립터 인자/핸들 인자

myaddr : 할당하기를 원하는 주소 정보를 지닌 sockaddr_in 구조체 변수의 포인터. 

※ sockaddr 형태로 형변환 해서 넘겨야 한다. 

※ 프로토콜에 독립적으로 사용하기 위해서. void 포인터 보다 먼저 이 함수가 개발되었다. 

※ FAR 포인터는 과거의 잔재. 현재는 무시한다.

addrlen/namelen : 주소 정보 구조체의 길이.


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

※ SOCKADDR_IN = struct sockaddr_in

※ SOCKADDR = struct sockaddr

※ 서버 프로그래밍을 할 때는 htons(INADDR_ANY);를 사용한다.


3-9 WSAStringToAddress & WSAAddressToString

주소 정보를 나타내는 문자열을 가지고 주소 정보 구조체 변수를 채운다.

※ 윈도우즈 기반 주소 변환 함수.

#include<winsock2.h>

INT WSAStringToAddress(

LPTSTR AddressString, // 점이 찍힌 십진수 표현(포트정보 포함)과 같은 주소 정보 문자열 포인터.

INT AddressFamily,      // 주소 정보 문자열이 속한 주소 체계(AF_INET)

LPWSAPROTOCOL_INFO lpProtocolInfo,   // 프로토콜 제공자를 설정. 일반적으로 NULL.

LPSOCKADDR lpAddress,  // 주소 정보 구조체 변수 포인터

LPINT lpAddressLength);  // lpAddress 포인터가 가리키는 버퍼의 크기.

리턴 : 성공시 0, 실패시 SOCKET_ERROR


#include<winsock2.h>

INT WSAAddressToString(

LPSOCKADDR lpsaAddress, // 문자열로 변환할 주소 정보를 가진 구조체 포인터

dwAddressLength,              // lpsaAddress 포인터가 가리키는 변수의 크기

LPWSAPROTOCOL_INFO lpProtocolInfo,   // 프로토콜 제공자를 설정. 일반적으로 NULL.

LPTSTR lpszAddressString,  // 문자열로 변경된 결과를 저장할 버퍼 포인터.

LPDWORD lpdwAddressStringLength);     // lpszAddressString 버퍼의 크기.

리턴 : 성공시 0, 실패시 SOCKET_ERROR




반응형
반응형

2-1 프로토콜 

컴퓨터 상호간의 대화에 필요한 통신 규약

혼돈의 여지가 있으면 안되고 잘 정의(FM)되어야 한다.

※ 안 좋은 예 : 서버에서 accept 하자마자 읽는 연산을 수행.


2-2 소켓의 생성

기본적인 통신 도구

프로토콜에 독립적 : 사용자가 프로토콜을 정해주어야 한다는 점에서...

데이터 전송 형태(타입)를 지정해야 한다.


2-3 프로토콜 체계

#include<sys/types.h>

#include<sys/socket.h>

int socket(int domain, int type, int protocol);

domain : 프로토콜 체계. family

(여러 가지 프로토콜이 합쳐져서 체계를 이룬다.)

PF_INET : IPv4 (4바이트 2^32승개의 IP)

PF_INET6 : IPv6 (16바이트 2^128승개의 IP, 약 10의 38승)

PF_LOCAL : Local 통신을 위한 UNIX 프로토콜

PF_PACKET : Low level socket을 위한 인터페이스

PF_IPX : IPX 노벨 프로토콜

※ 127.0.0.1 : loopback address


2-4 소켓의 타입

type : 형태

1. 연결지향형(connection oriented) 

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

에러나 데이터 오류 없이 전달.

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

 => write함수를 2번호출한다고 해서 read함수가 꼭 2번 호출될 필요가 없다. 


2. 비연결지향형(connectionless)

예시) 우편이면 일반우편, 빠른 우편, 등기 등.

전송되는 순서에 상관없이 가장 빠른 전송.

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

데이터의 경계가 존재하고 그 크기는 제한되어 있다.

 => write함수를 2번 호출하면 read함수가 꼭 2번 호출해야 한다.


2-5. 프로토콜의 선택

protocol : 프로토콜을 구체화할 때 필요하다. (raw_socket을 다룰 때 유용하다.)

IPPROTO_TCP : TCP를 기반으로 하는 소켓을 생성. PF_INET + SOCK_STREAM

IPPROTO_UDP : UDP를 기반으로 하는 소켓을 생성. PF_INET + SOCK_DGRAM


※ 파일 핸들도 정수형 데이터이다. 

int a = socket(...); = SOCKET a = socket(...);

앞으로 소켓의 데이터 형이 바뀔 것을 대비해서 SOCKET을 사용하는 것이 좋다.


소켓의 종료

리눅스 

#include<unistd.h>

int close(int filedes);

리턴 : 성공시 0, 실패시 -1

filedes : 닫아줄 파일의 파일 디스크립터


#include<winsock2.h>

int closesocket(SOCKET s);

리턴 : 성공시 0, 실패시 SOCKET_ERROR

s : 닫아줄 소켓의 핸들

반응형
반응형

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

네트워크 : 호스트(End-system)들을 연결하는 시스템

호스트 : PC, workstation, PDA

인터넷 : 멀리 떨어진 둘 이상의 네트워크가 연결되 이뤄진 거대한 네트워크 -> 라우터 : 이기종 네트워크를 연결하는 장비


1.2 소켓 이해하기

클라이언트/서버 모델

기계 아님. 

서버 : 연결 요청을 기다린다.

 - Iterative(반복적인) Server : 한 순간에 하나의 클라이언트에게 응담

 - Concurrent Server : 동시에 여러 클라이언트에게 응답한다.

클라이언트 : 서버에 요청하고 응답을 기다리는 호스트.


네트워크 프로그래밍

네트워크로 연결된 두 호스트 간의 데이터 송수신

소켓 : 원격에 존재하는 두 호스트를 연결시켜 주는 매개체. 운영체제에서 제공한다.

(소켓을 꽂으면 전원을 받는다.)


서버 소켓(리눅스 함수)

소켓 생성(전화기 구입) - socket


#include<sys/types.h>

#include<sys/socket.h>

int socket(int domain, int type, int protocol);

리턴 : 성공시 파일 디스크립터 실패시 -1


IP 주소, 포트 할당(전화번호 할당) - bind

#include<sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

리턴 : 성공시 0, 실패시 -1


연결 요청 대기 상태(케이블에 연결) - listen

#include<sys/socket.h>

int listen(int sockfd, int backlog);

리턴 : 성공시 0, 실패시 -1


연결 수락(수화기를 든다) - accept

#include<sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, int *addrlen);

리턴 : 성공시 파일 디스크립터 실패시 -1


클라이언트 소켓

소켓 생성(전화기 구입) - socket


연결 요청(전화 걸기) - connect

int connect(int sockfd, struct sockaddr *addr, int addrlen);

리턴 : 성공시 0, 실패시 -1


리눅스 프로그램 컴파일(링크) 하는 법 : gcc ?.c -o ?

실행하는 법 : ./?

※ 로컬 컴퓨터의 IP : 127.0.0.1

※ 당연히 서버부터 수행한다.


1.3 파일 조작하기

리눅스에서는 모든 것(소켓등)을 파일로 간주한다.

파일의 생성, 삭제, 데이터 입력 및 출력.

관리는 운영체제가 한다.


저수준 파일 입출력 

저수준은 시스템이 직접 제공해 준다는 뜻.

표준입력 : 기본은 키보드, fd 0번

표준출력 : 기본은 모니터, fd 1번

표준에러출력 : 기본은 모니터, 버퍼가 없음. fd 2번

※ 전송은 출력, 수신은 입력은 같은 의미.

※ 표준입출력함수 : ANSI 표준에서 제공해 주는 함수. printf, scanf등...


파일 디스크립터(file descriptor) 

시스템이 만든 것을 가리키기 좋게 하기(포인터) 위해 시스템이 사용자에게 건내주는 숫자값

윈도우의 핸들과 비슷.

모든 파일을 관리하기 위해 운영체제에서 파일 디스크립터를 할당한다.

※ 파일 디스크립터는 redirection가능.


파일 열기

#include <fcntl.h>

#include<sys/types.h>

#include<sys/stat.h>


int open(const char *path, int flag);

리턴 : 성공시 파일 디스크립터, 실패시 -1

path : 파일에 대한 경로

flag : 모드 설정. |(bit wise)를 통해 연산가능.

O_CREAT : 파일이 없을 때 파일 생성. 

O_TRUNC : 파일이 있다면 새로 생성.

O_RDONLY : 읽기 전용 모드


파일 닫기

#include<unistd.h>

int close(int filedes);

리턴 : 성공시 0, 실패시 -1

filedes : 닫아줄 파일의 파일 디스크립터


데이터 쓰기

#include<unistd.h>

ssize_t write(int filedes, const void * buf, size_t nbytes);

filedes : 데이터 전송 영역의 파일 디스크립터.

buf : 전송할 데이터를 가지고 있는 버퍼(데이터)의 포인터. 

nbytes : 전송할 데이터의 바이트수.

ssize_t = signed int, size_t = unsigned int

※ 타입이름을 새로 정의하는 이유는 다른 시스템에서 실행시키기 위해(코드 확장성)

=> 소스 코드를 바꾸지 않고 컴파일만 다시 하면 새로운 시스템에서 잘 돌아간다.


데이터 읽기

#include<unistd.h>

ssize_t read(int filedes, void *buf, size_t nbytes);

filedes : 데이터를 수신 받을 대상을 가리키는 파일 디스크립터.

buf : 수신한 데이터를 저장할 버퍼의 포인터

nbytes : 수신할 최대 바이트수.


1-4 윈도우즈 기반으로 구현하기

WinSock을 위한 헤더 및 라이브러리 설정.

1. #include<winsock2.h>

2. #pragma comment(lib,ws2_32.lib)

ws2_32.lib을 위와 같이 라이브러리를 링크

3. winsock 라이브러리 초기화(standby) 및 해제(리소스 반환)


Winsock 초기화하기

#include<winsock2.h>

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

wVersionRequested : 프로그램에서 요구하는 winsock 최상위 버젼을 알려준다.

WORD : 16비트 unsigned int

예) Version 3.4 = MAKEWORD(3 /*주버젼*/,4 /*부버젼*/) =  0x0403 

lpWSAData : WSADATA 타입의 변수 포인터.


Winsock 해제하기

#include<winsock2.h>

int WSACleanup();


기본적인 Template

int main(int argc, char **argv)

{

    WSADATA wsaData;

    if(WSAStartup(MAKEWORD(2,2), &wsaData) !=0)

         error_handling("WSAStartup() error!");

    ...

    WSACleanup();

    return 0;

}


소켓의 생성

#include<winsock2.h>

SOCKET socket(int af, int type, int protocol);

리턴 : (정수형 데이터 타입)성공시 소켓 핸들, 실패시 INVALID_SOCKET 리턴.


주소와 포트 할당

#include<winsock2.h>

int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);

리턴 : 성공시 0, 실패시 SOCKET_ERROR


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

#include<winsock2.h>

int listen(SOCKET s, int backlog);

리턴 : 성공시 0, 실패시 SOCKET_ERROR


연결 수락

#include<winsock2.h>

SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

리턴 : (정수형 데이터 타입)성공시 소켓 핸들, 실패시 INVALID_SOCKET 리턴.


연결 요청

#include<winsock2.h>

int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);

리턴 : (정수형 데이터 타입)성공시 소켓 핸들, 실패시 INVALID_SOCKET 리턴.


데이터 출력

#include<winsock2.h>

int send(SOCKET s, const char FAR *buf, int len, int flags);

리턴 : 성공시 전송한 바이트 수, 실패시 SOCKET_ERROR

s : 전송할 호스트에 연결된 소켓의 핸들

buf : 전송할 데이터를 저장하고 있는 버퍼의 포인터

len : 전송할 바이트 수를 인자로 전달

flags : 여러가지 옵션을 설정.


데이터 입력

#include<winsock2.h>

int recv(SOCKET s, char FAR *buf, int len, int flags);

리턴 : 성공시 수신한 바이트 수, 실패시 SOCKET_ERROR

s : 수신할 영역을 나타내는 소켓의 핸들.

buf : 수신할 데이터를 저장한 버퍼의 포인터

len : 수신할 최대 바이트수

flags : 여러가지 옵션을 설정.


※ 리눅스에도 send, recv함수가 있지만 리눅스에서는 소켓도 파일로 처리한다는 것을 강조하기 위해 read, write함수를 사용하였고 윈도우즈에서는 read, write함수가 없기 때문에 send, recv함수로 소켓 입출력을 수행한다.

반응형

+ Recent posts