2016년 6월 17일 금요일

뇌자극 TCP_IP 프로그래밍 27강 요약

27강 웹서버 개발

1. HTTP 와 HTML

HTTP - 데이터 요청과 응답에 관한 프로토콜
HTML - 데이터의 형식을 정의하기 위한 프로토콜

HTTP 프로토콜의 기본

요청 메시지는 3개의 필드로 구성된다.

요청방법 | 요청페이지 | 프로토콜

요청 방법

GET - 자료(웹문서, 이미지 등)의 전송을 요청한다.
HEAD -  GET 와 비슷하지만 자료에 대한 부가정보를 포함한다.
POST - 서버가 처리해야 할 자료를 보낸다. 즉, 데이터 처리를 요청한다.
PUT - 자료를 저장한다.
DELETE - 자료를 삭제한다.

-요청할 때 URL : GET 이라면 요청할 페이지고, POST 라면 클라이언트가 보낸 자료를 처리할 서버의 URL 이다.
-프로토콜 버전 :  HTML 프로토콜의 버전을 명시한다. 여기서는 1.1으로 했다.

응답 역시 HTTP 형식에 맞게 전송해야 한다. HTTP 정보는 다음과 같은 형식을 가진다.
GET 요청에 응답할 수 있다.

1) HTTP/1.1 200 OK //HTML 프로토콜 버전과 클라이언트 응답 코드 정보
2) Date : Sun, 29, Aug 2016 15: 02:01 GMT //문서를 전송한 시간
3) Server : Apache/2.24  (Unix) PHP/5.2.0 //웹서버 프로그램의 정보
4) Content-length :  118 //문서의 크기
5) Connection : close //연결방식, 웹서버는 정보를 전달한 다음 곧바로 연결을 끊는다.
6) Content-Type : text/html //문서의 형식(텍스트만 사용)

웹서버 프로그램 개발

클라이언트의 요청을 받은 웹서버는 파일 시스템에서 파일 정보를 읽어서 클라이언트에 전송한다.
또는 회부의 프로그램을 실행해서 그 결과를 클라이언트에 전송하기도 한다.
외부 프로그램의 정보 입출력은 GCI 규격을 이용한다.

2. 서버 프로그램 개발

기능 정의

1) 클라이언트의 요청을 받아서, 하드디스크 상의 웹 문서를 찾아서 전송한다.
2) 컨텐트 유형은 오직 text/html 만 다룬다.
3) 에러 코드는 '200 OK', '404 Not Found', '500 Internal Server Error' 만 지원한다.
4) 요청 방식은 GET 만 지원한다. POST 요청에 대해서는 '500 코드'로 에러 처리한다.

웹서버 프로그램 myserver.c

소스 코드는 주석과 함께 깃허브에 올렸다.

소스 코드 받기

3. 테스트

myserver 프로그램은 웹문서가 위치하는 디렉터리를 실행인자로 실행한다.
웹문서의 루트 디렉터리는 webDoc 라고 한다면...

./myserver webDoc

테스트용 웹페이지 index.html 이다.

<html>
<body>
     <h1>Hello World!!!</h1>
</body>
</html>

이로서 뇌를 자극하는 네트워크 프로그래밍 공부 리뷰를 마쳤다.
길고 긴 여정(?) 이었다.
이론 공부를 하고, 코드를 쳐보면서 실습해보고, 블로그에 게시하면서 복습하니
공부가 잘 되는 것 같다.
다음 공부는 수학 + 알고리즘을 해보려고 한다.
언제가 될지 모르겠지만 공부하면서 간간히 복습 차원에서 요약 정리를 하고자 한다.

2016년 6월 15일 수요일

뇌자극 TCP_IP 프로그래밍 26강 요약

26강 파일 전송 프로그램 개발

이제부터는 여태 배운 것을 토대로 프로그램을 만들어본다.(여기까지 공부하느라 힘들었다 -_-)
파일 전송 프로그램 개발을 해보자.

1. 서비스 프로그램의 구성

서버/클라이언트 모델로 만든다.
서버 : 파일의 목록을 가지고 있다가 클라이언트가 요청하면 파일을 전송해준다.
클라이언트 : 서버에 있는 파일 목록을 조회하고, 파일 업로드, 파일 다운로드를 한다.

대략적인 모양은 다음과 같다.

              read                       파일 업로드
| 파일  |  -------->    | 파일 |    ------------------->  | 클라이언트 |
| 목록  |  <--------    | 서버 |    <-------------------  |                |
              write           |           파일 다운로드
                               |
                            | DB |
                           
그냥 책에 있는거 사진으로 찍어서 올릴껄 그랬다-_-;

2. 서비스 프로그램 개발 준비

프로그램 명세서

1) 서비스 내용 : 인터넷을 통한 파일 송수신
2) 기능 : 파일 다운로드, 파일 업로드, 파일 목록 전송
3) 파일 정보 저장 :  DB 서버는 파일 업로드 기능을 제공한다. 언제, 누가, 어떤 파일을 업로드 했는지 정보가 있어야 한다. 사용할 DBMS 는 SQLite 다.
4) 입출력 구조 :  서버는 여러 클라이언트를 동시에 처리해야 한다. 멀티 프로세스 방식을 사용한다.

DB 명세서

DB 명  : mydb.db
table 명  : file_info

필드 이름 | 데이터 크기 | 설명

name      | char(64)      | 파일 이름
ip           | char(64)      | 파일을 올린 IP 주소
up_date   | datetime     | 파일을 올린 시간
count      | int             | 다운로드 개수
size        | int             | 파일 크기

애플리케이션 프로토콜

클라이언트는 다음과 같이 간단한 구조체로 요청을 보낸다.
struct CProtocol
{
   int command;
   char f_name;
};
서버는 요청을 분석해서 구조체를 먼저 전송하고 그 다음 실제 요청에 대한 응답 데이터를 전송한다.
서버는 command 멤버 변수를 통해서 클라이언트에 응답 정보를 전송할 것이다.

3. SQLite DB

SQLite 설치

apt-get install sqlite3
apt-get install libsqulite3-dev

DB와 테이블 생성

cd DB를 생성할 경로
sqlite mydb.db  //mydb.db를 생성한다.

sqlite> 가 뜨는데 그 옆에 아래와 같이 치면 테이블이 생성된다.

sqlite> create table file_info
     ...>(
     ...>name char(64),
     ...>ip char(64),
     ...>up_date datetime,
     ...>count int,
     ...>size int
     ...>);

데이터를 입력해보자.
insert into file_info values('test.txt', '127.0.0.1', '2016/6/16 14:18:00', 0, 1000);

데이터를 조회해보자
select * from file_info;

주요 SQLite 함수

DB 파일 열기
sqlite3_open(const char *filename, sqlite3 **ppDb);

filename : 열고자 하는 DB 파일이름
ppDb : 열린 DB를 가리키는 포인터

sql 질의어 실행
sqlite3_exec(sqlite3 *pDb, const char *sql, int (*callback)(void*, int, int(*callback)(void*, int, char **, char**)void *arg, char **errmsg))

pDb : 열린 DB
sql : SQL 질의어
callback : SQL 가 실행되었을 때, 데이터를 처리할 콜백 함수
arg :  콜백 함수의 첫 번째 매개변수로 넘어갈 값을 가리키는 포인터
errmsg : 에러 메시지

열린 DB 닫기
sqlite3_close(sqlite3 *pDb)

pDb : 열린 DB

4. 서버 프로그램 개발

프로그램 흐름

총 3가지의 일을 해야 한다. (파일 목록, 파일 업로드, 파일 다운로드)

1) 클라이언트가 파일 목록을 요청하면
2) file_server 은 SQLite에 저장된 파일 정보를 읽어와
3) 클라이언트에 전송한다.

1) 파일 업로드를 요청하면
2) 데이터를 하드디스크에 파일로 저장한다.
3) 저장에 성공하면 파일 정보를 SQLite에 저장한다.

1) 파일 다운로드를 요청하면
2) 하드디스크에 동일한 파일이 있는지 확인한다.
3) 있다면 클라이언트에 전송한다.

5. 클라이언트 프로그램 개발

클라이언트는 CLI 방식으로 제작한다.
그리고 프로그램 실행인자를 이용한다.

프로그램 사용법은 다음과 같다. 프로그램 명은 flie_cli 다.

./file_cli -h -i [ip] -u [upload filename] -d [download filename] -l

-i : 연결할 서버의 인터넷 주소
-u [업로드 파일명] : 업로드할 파일 이름
-d [다운로드 파일명] : 다운로드할 파일 이름
-l : 파일 목록 요청(L의 소문자다)
-h : 도움말 출력

예) ./file_cli -i 127.0.0.1 -l <-----파일 목록 확인이다.

파일을 다룬다는 것 외에는 에코 클라이언트 프로그램과 거의 같다.

프로그램은 주석과 함께 깃허브에 올렸다.

소스 코드 받기

뇌자극 TCP_IP 프로그래밍 25강 요약

25강 인터넷 서비스 프로그램 개발

1) 리눅스 데몬 프로그램 

서버 프로그램은 사용자와 상호작용할 필요가 없다.
서버는 테스트를 위한 목적으로 실행할 때 제외하고는
사용자와 상호작용하지 않는 상태로 실행해야 한다.
그래야 실행환경이 보장된다.

리눅스는 상호작용하지 않는 상태로 백그라운드에서 실행되는 프로세스를 데몬 프로세스라고 한다.

데몬 프로세스는 일반적으로 os의 시작과 더불어 구동되고 특별한 일 없으면 os가 종료할 때 종료된다.

GUI와 CLI

상호 작용은 크게 두 가지 방식으로 이루어진다.

텍스트를 이용하는 방법 - CLI (Command Line Interface)
아이콘, 메뉴, 버튼, 이미지를 이용하는 방법 - GUI (Graphic User Interface)

데몬 프로세스가 되기에서는 3가지 조건을 만족시켜야 한다.

1) 부모 프로세스로부터 독립 시킨다. 
   자식 프로세스는 부모 프로세스로 부터 영향을 받는다.
   쉘을 종요하면 자식 프로세스까지 함께 종료된다.
   이를 막기 위해서 부모 프로세스로 부터 독립되어야 한다.

2) 표준 입출력과 표준 에러를 닫는다. 
   사용자와의 상호작용은 키보드와 모니터로 이루어지므로
   표준 입출력과 표준 에러를 닫아준다.

3) 새로운 세션을 만든다. 
   일반적으로 세션은 로그인과 함께 만들어지며 그룹을 포괄하는 상위 개념이다.
   데몬 프로세스는 모든 환경에서 독립되어야 하므로 새로운 세션을 가져야 한다.

부모 프로세스로부터 독립시키기

fork 함수를 호출해서 자식 프로세스를 만든 뒤 부모 프로세스를 종료하면 된다.

표준 입출력 닫기

close(0);
close(1);
close(2);

새로운 세션 만들기

#include <unistd.h>
pid_t setsid(void);

데몬 프로세스 만들기

1) 자식 프로세스를 만든 뒤, 부모 프로세스를 종료한다.
2) 자식 프로세스의 표준 입력/출력/에러를 닫는다.
3) setid 함수를 호출한다.

아주 간단한 데몬 생성 함수를 만들어보자.

void make_daemon()
{
  pit_t pid;
  pid = fork();  //자식 프로세스 만들기

  //부모 프로세스 종료하기
  if(pid < 0)
    exit(0);
  else if(pid != 0)
    exit(0);

  close(0);  //표준 입력 닫기
  close(1);  //표준 출력 닫기
  close(2);  //표준 에러 닫기

  setsid();  //새로운 세션 만들기 
}

데몬 프로세스와의 상호작용

데몬 프로세스는 사용자와 상호작용을 할 수 없다.
그러나 아래와 같은 정보를 확인 할 수 있어야 하기에 상호작용을 완전히 포기할 수는 없다.

1) 프로그램 활동 정보를 확인할 수 있어야 한다.
2) 프로그램 오류 정보를 확인할 수 있어야 한다.
3) 서버 작동 중에 정보를 확인하거나 직접 조작할 수 있어야 한다.

1번 2번은 로그 파일을 이용한다.
3번은 IPC를 이용해서 서버/클라이언트 방식으로 상호 작용한다.

포그라운드와 백그라운드

데몬 프로세스는 부모 프로세스에서 독립한 자식 프로세스다.
독립한 자식 프로세스는 전면에서 실행된다.
전면에서 실행되는 이 상태를 포그라운드 상태라고 한다.
이때 부모 프로세스는 멈춤 상태가 된다.
Ctrl + z 키를 누르면 포그라운드 상태의 프로세스가 중단되고
부모 프로세스가 실행되면서 프롬프트가 뜬다.
여기에 다시 bg 명령을 내리면 부모 프로세스도 실행 상태가 유지되면서
자식 프로세스도 백그라운드에서 실행된다.
백그라운드 프로세스는 터미널의 주도권이 없다.

백그라운드 프로세스는 fg 명령을 이용해서 포그라운드 상태로 전환할 수 있다.
쉘은 여러 프로세스를 백그라운드 상태로 동시에 실행할 수 있는데,
 jobs 명령어를 이용해서 백그라운드 상태에 있는 프로세스 목록을 확인할 수 있다.
프로그램이 처음부터 백그라운드 모드로 실행되도록 하려면
프로그램 마지막에 & 옵션을 추가해주면 된다.

./testjob1 & ./testjob2 & ./testjob3
jobs
[1]     실행중     ./testjob1 &
[2]-    실행중     ./testjob2 &
[3]+   실행중     ./testjob3
fg 1 //1번 프로세스(tsetjob1)가 포그라운드 상태가 된다.

2. 인터넷 서비스 데몬

서버는 클라이언트의 연결을 기다리는 영역과 클라이언트와 통신하는 영역으로 나눌 수 있다.

클라이언트의 연결을 기다리는 영역을 전문적으로 기다리는 프로그램을 만든다고 가정하면

1) 여러 포트에서 클라이언트를 기다린다.
2) 클라이언트 요청이 들어오면 자식 프로세스를 만든다.
3) 각 포트 번호에 연결된 프로그램을 execl 함수로 실행한다.
4) 소켓을 dup2 함수를 이용해서 실행할 프로그램의 표준입력으로 복사한다.
5) 표준 입력을 이용해서 클라이언트와 통신할 수 있게 된다.

dup, dup2 함수
파일 지정번호를 복사한다.

#include <unistd.h>

int dup(int oldfd);  //가장 작은 파일 지정번호로 복사한다. 
int dup2(int oldfd, int newfd);  //newfd가 이미 열려있는 파일지정 번호라면 닫은 다음 새로 만든다.

리눅스는 xinetd라는 시스템 프로그램을 제공한다. 인터넷 서비스 데몬이라고 부른다.
이 프로그램은 유저가 설정한 포트를 기다리다가 클라이언트가 연결하면 포트에 연결된 프로그램을 실행해서 클라이언트 요청을 처리한다.

xineted 설치법
sudo apt-get install xinetd

포트와 프로그램의 연결관계 및 통신 프로토콜의 정보를 담은 설정 파일이 필요하다.
/etc/xinetd.d 디렉토리 밑에 파일로 존재한다.
이들 파일은 하나의 서비스에 대응되며, 다음과 같은 내용을 포함한다.

service myecho
{
   disable = no //서비스의 활성활 여부(yes면 비활성화, no면 활성화)
   port = 3600  //대기 포트 번호
   type = UNLISTED //서버의 형식 (표준 형식 = INTERNAL, 비표준 형식 = UNLISTED)
   protocol = tcp //통신 프로토콜(tcp 또는 udp)
   socket_type = stream //프로토콜과 쌍으로 지정한다
   wait = no //no로 지정하면 instances에서 지정한 만큼의 클라이언트를 허용한다.
   user = root //연결된 클라이언트를 어떤 유저 권한으로 실행할지 지정
   instances = 5 //최대 동시 접속 클라이언트 수
   server = /usr/local/bin/echo_server_xinetd //호출할 프로그램 지정(완전 경로 사용)
}

xinetd 설정 파일을 수정한 후 xinetd를 재시작하면 적용된다.

/etc/init.d/xinetd restart

인터넷 서비스 데몬의 장단점

장점 - 안정된 서비스 가능
단점 - 고성능을 보장 못함

소스 코드 받기

2016년 6월 12일 일요일

뇌자극 TCP_IP 프로그래밍 24강 요약

24강 IPv6 프로그래밍

1. IPv4의 한계

IPv4는 0.0.0.0 ~ 255.255.255.255 로 32비트의 크기를 가진다.
대략 4억개의 장치에 유일한 IP를 할당할 수 있다.
그러다 보니 IP가 부족해서 대안으로 나온 것이 IPv6다.

2. IPv6 를 이용한 인터넷 인프라의 효과적인 활용

IP 주소 부족 문제 해결

IPv6 의 가장 큰 특징은 인터넷 주소의 크기를 기존 32비트에서 128비트로 확장했다.
거의 무한에 가까운 IP 를 할당 할 수 있다.

효율성

IPv6 는 IPv4 보다 단순한 구조의 헤더를 사용한다. 확장 헤더를 통해 기능을 쉽게 확장 시킬 수 있다.

버전 | 트래픽 클래스 | 흐름 라벨 | 패이로드 길이 | 다음 헤더 | 홉제한
                                발신자 주소
                                목적지 주소

이런식으로 생겼다.

주소 자동 설정

IPv6는 인터넷에 접속하는 순간 자동적으로 네트워크 주소를 부여 받는다.
네트워크 관리자로 부터 IP 주소를 부여 받아 수동으로 설정해야 했던 IPv4에 비하면 편하다

이동성

IPv6는 네트워크의 위치와 상관 없이 IP주소를 유지할 수 있다.

3. IPv6 환경 만들기

512비트 씩 끊어서 16진수로 표현한다.
각 자리 구분은 : 로 한다.

예) 3ffe : ffff : 0100 : f140 : 0210 : a4ff : fee3 : 9566

각 블럭의 앞자리에 오는 0은 생략해서 표현할 수 있다.

예) 3ffe : ffff : 0100 : f101 : 0 : 0 : 0 : 1 => 3ffe : ffff : 0100 : f101 : : 1

루프백 주소는  ::1 이다.

리눅스와 IPv6 

리눅스는 IPv6 주소가 부여된 상태로 부팅된다. ifconfig 로 확인할 수 있다.
할당된 IPv6 주소를 사용할 수 있는지 테스트는 ping6 명령어를 사용하면 된다.

ping6 ::1

4. IPv6 프로그램 개발

주소 체계만 다르므려 소켓을 만들고 읽고 쓰는데는 차이가 없다.
주소 체계 관련 부분만 다르다.

소켓 구현 

socket 함수를 그대로 사용한다. 소켓의 주소 영역만 AF_INET6 으로 해서 만들면 된다.

s = socket(AF_INET6, SOCK_STREAM, 0);  //TCP
s = socket(AF_INET6, SOCK_DGRAM, 0);  //UDP

소켓에 IPv6 주소를 묶어주려면 sockaddr_in6 구조체를 사용해야 한다.

#include <netinet/in.h>
struct sockaddr_in6
{
   u_int16m_t  sin6_family;    //AF_INET6
   u_int16m_t  sin6_port;    //Port 번호
   u_int32m_t  sin6_flowinfo;   //IPv6 flow information
   struct in6-addr   sin6_addr;  //IPv6 주소
   u_long   sin6_scope_id;  //이더넷 카드를 지정하기 위해 사용
}

이더넷 카드의 인덱스 값은 if_nametoindex 함수로 가져올 수 있다.
만약 eth0 의 인덱스 값을 가져오길 원한다면, 다음과 같이 하면 된다.

if_nametoindex("eth0");

IPv6 에 대응하는 bind 함수의 사용법

struct sockaddr_in6 sin6;

sin6.sin6_family = AF_INET6;
sin6.sin6_flowinfo = 0;
sin6.sin6_port = htons(3500);
sin6.sin6_addr = in6addr_any; //IPv4에서 INADDR_ANY 와 같은 의미로 사용된다.

IPv6 용 인터넷 주소 변환 함수

서버는 주소에 in6addr_any를 사용해서 모든 주소에 대해서 기다리면 되낟.
클라이언트는 서버의 IP 주소를 지정해야만 한다.
IPv6에서는 inet_addr 함수 대신에 inet_pton 함수를 이용해서 : 형식의 주소로 변환할 수 있다.

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

int inet_pton(int af, const char *src, void *dst);

매개 변수

af : 변환할 IP 프로토콜을 명시한다. IPv4 : AF_NET, IPv6 : AF_INET6 을 지정
src :  변환할 주소를 입력한다. IPv6 라면 : 로 이루어진 주소를 입력한다.
dst :  변환 결과를 반환 값으로 전달한다.

IPv6 기반 에코 서버 프로그램

socket 함수의 매개변수로 AF_INET6 를 지정하고,
struct sockaddr_in6 구조체를 사용했다는 것을 제외하고
IPv4 버전의 에코 서버와 차이가 없다.

IPv6 기반 에코 클라이언트 프로그램

IPv6 는 IPv4를 기본적으로 지원하기 때문에
하나의 소켓으로 두 개의 서로 다른 주소 체계를 모두 지원한다.

5. IPv4와 IPv6 모두를 지원하는 소켓 프로그램 개발

IPv4 와 IPv6 병행 서버 개발

소켓을 만들 때, 인터페이스의 주소 정보를 확인해서
IPv4 전용 소켓, IPv6 전용 소켓, 모두를 사용하는 소켓으로 구분하여 만들어여 한다.

getaddrinfo 함수로 소켓 주소 정보 가져오기

getaddrinfo 함수로 컴퓨터와 서비스에 대한 네트워크 소켓 주소 정보를 가져올 수 있다.

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

int getaddrinfo(const char *node, const char *service,
                      const struct addrinfo *hints, struct addrinfo **res);
매개변수

node : 정보를 알아오기 원하는 인터넷 주소로 IPv4 주소, IPv6 주소, 호스트 이름을 지원한다.
service : /etc/services 에 포트와 대응되는 인터넷 서비스의 이름이다.
addrinfo : 가져오기 원하는 정보에 대한 힌트를 지정한다. getaddrinfo 함수는hints 값에 일치하는 네트워크 정보만 가져온다.
res : 가져온 네트워크 주소 정보는 addrinfo 구조체에 복사된다.

struct addrinfo  {
  int ai_flags;
  int ai_family;
  int ai_socktype;
  int ai_protocol;
  size_t ai_addrlen;
  struct sockaddr *ai_addr;
  char *ai_canonname;
  struct addrinfo *ai_next;
};

매개변수 

ai_flags : 추가 옵션
ai_family : AF_INET, AF_INET6 와 같은 주소 영역이다.
ai_socktype : SOCK_STREAM, SOCKDGRAM 과 같은 소켓 타입
ai_protocol : 소켓 주소 프로토콜
ai_addrlen : 주소 정보를 담고 있는 구조체 ai_addr의 크기
ai_addr : 주소 정보를 담는 구조체로 IPv4, IPv6 형식 모두를 지원한다.
ai_canonname
ai_next

AF_UNSPEC는 따로 주소지정을 하지 않겠다는 의미로 가리지 않고 주소 정보를 가져온다.

소스 코드 받기

뇌자극 TCP_IP 프로그래밍 23강 요약

23강 멀티 캐스팅

1. 캐스트

컴퓨터 네트워킹에서 정보를 배포하는 것은 배포라는 용어 대신 캐스트(cast) 라는 용어를 사용한다.
인터넷은 크게 세 가지 캐스팅 방식을 사용한다.

유니캐스트

인터넷 상에서 노드와 노드 사이에 통신 선로가 개설되어서 1:1로 통신하는 방식을
유니 캐스트(uincast)라고 한다.
멀티 스레드나 멀티 프로세스의 경우는 1:1 통신 선로를 다수 개설한 유니캐스트 방식이다.

브로드 캐스트

말 그래도 방송(broadcast)이다. 따로 대상을 정하지 않고,
해당 네트워크 영역에 있는 모든 컴퓨터에 정보를 뿌린다.
브로드 캐스트는 지역 네트워크 내의 컴퓨터로 데이터를 전송한다.

지역 네트워크를 벗어나서 데이터가 방송되지는 않는다.
일반적으로 주변의 네트워크 상황을 알고 싶을 때 사용한다.
라우터는 패킷의 IP 정보를 읽어서 어느 쪽으로 패킷을 보내야 할지 결정하는 일을 한다.

그러기 위해서는 주변의 라우터의 정보를 모두 알고 있어야 한다.
이때 브로드캐스트 방식으로 주변 모든 라우터에게 ARP 요청을 보낸다.

브로드 캐스트 주소는 해당 네트워크 영역에서 가장 큰 주소다.
예를 들어 192.168.16.0 주소 영역에서는 192.168.16.255 가 브로드 캐스트 주소가 된다.

멀티 캐스트

멀티캐스트(multicast)는 특정 네트워크 상에서 해당 채널에 포함된 모든 컴퓨터에 데이터를 전송한다.
브로드캐스트는 해당 네트워크의 모든 컴퓨터에 전송하지만,
멀티캐스트는 채널의 개념이 있어서 해당 채널에 포함되어 있어야지만 데이터를 받는다.

캐스트별 성능과 용도

유니캐스트는 1:1 통신으로서 대부분의 서비스에 무난하게 사용할 수 있지만
다수의 유저에게 동일한 데이터를 전송해야 하는 서비스에는 비효율적이다.
대상마다 연결을 맺어야 하기에 프로그래밍 과정이 복잡해지고 서버의 부하도 크다.

브로드캐스트 또는 멀티캐스트로 데이터를 전송하면,
해당 데이터는 라우터로 전송된 다음 라우터에서 복사되어 다른 컴퓨터로 전달된다.
그만큼 서버는 데이터 복사에 따를 부담을 덜게 된다.

모두에 뿌리는 브로드 캐스트 보다는 수신하기 원하는 채널에만 뿌리는
멀티캐스트가 더 효율적으로 네트워크 자원을 사용한다.

2. 멀티 캐스트의 활용과 단점

멀티 캐스트를 제대로 할려면 멀티캐스트를 지원하는 라우터가 필요하다.
인터넷에 멀티캐스트를 지원하지 않는 라우터가 있을 수 있다는 것이 단점이다.

이 문제를 해결하기 위해서 MBone(Multicast Backbone)이라는 멜터캐스팅 지원망이 있다.
MBone은 멀티캐스트가 가능한 네트워크들을 상호 연결한 네트워크 망이다.

당분간은 MBone 망이 대안일 수 있다. 대신 한정된 라우터를 사용하고 있으므로
인터넷의 대역폭을 제대로 활용하지 못하는 문제가 있다.

3. 멀티캐스트 주소 환경

멀티캐스트는 특정 채널에 속한 컴퓨터만 전송한다. 따라서 그룹 관리 기능이 필요하다.
그룹들은 인터넷 주소로 관리되며 인터넷 주소는 클래스를 이용해서 체계적으로 관리되고 있다.

멀티캐스트 그룹을 관리하기 위한 D클래스가 예약되어 있다.
D클래스는 224.0.0.1 ~ 239.255.255.254 의 범위를 가진다.

4. 멀티캐스트 소켓 프로그래밍

멀티캐스트 클래스로 연결

멀티캐스트는 D클래스 주소를 사용한다. D클래스 주소는 어떤 컴퓨터를 가리키는 주소라기 보다는 멀티캐스트 데이터를 주고 받기 위한 논리적인 채널로 이해할 수 있다.

예를 들어 224.1.1.2라는 멀티캐스트 주소에 접속하는 것은 224.1.1.2 라는 이름의 채널에 접속한다고 생각하면 된다.

소켓 옵션을 이용한 멀티캐스트 소켓 만들기

멀티캐스트를 사용하기 위해서는 멀티캐스트 주소로 연결한 후
setsockopt 함수를 이용해서 멀티 캐스트 채널로 가입해주어야 한다.

IP_ADD_MEMBERSHIP : 멀티캐스트 채널의 멤버에 가입한다.
IP_DROP_MEMBERSHIP : 멀티캐스트 채널에서 탈퇴한다.

그리고 ip_mreq 구조체의 값을 채우면 소켓의 멀티캐스트를 제어할 수 있다.

struct ip_mreq mreq;
struct sockaddr_in sockaddr;

mreq.imr_multiaddr = sockaddr.sin_addr;
mreq.imr_interface.s_addr = htonl(INADDR_ANY);

setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

mir_multiaddr은 멀티캐스트 주소다.
imr_interface.s_addr은 데이터를 받을 인터페이스의 주소로
일반적으로 모든 인터페이스 주소를 통해서 데이터를 받기 때문에
INADDR_ANY를 옵션으로 사용한다.

소스 코드 받기

윈도우 멀티캐스트 프로그래밍

윈도우의 윈속도 BSD 소켓과 동일한 방식으로 멀티캐스트를 지원한다.
setsockopt 함수를 그대로 사용하며 값들 역시 BSD 소켓에서와 같다.
단지 멀티 스레드 기술로 입력과 출력을 동시에 다루고 있음만 다르다.

소스 코드 받기

2016년 6월 11일 토요일

뇌자극 TCP_IP 프로그래밍 22강 요약

22강 윈도우 IOCP 프로그래밍

1. IOCP 란?

IOCP의 원리

1) 소켓의 입력을 IOCP에 걸어 놓으면,
2) 소켓에 데이터 입력이 완료된 시점에 이벤트로 통지한다.
3) 데이터를 처리할 워커 스레드가 깨어나서
4) 입력 함수를 호출하고 데이터를 처리한다.

IOCP 지원 소켓 생성

입력 완료 통지는 중첩 소켓의 고유 기능이다. IOCP를 사용하려면 소켓을 중첩 모델로 열어야 한다.
IOCP 흐름은 다음과 같다

1) CP를 만든다. (소켓, 파일의 완료 결과를 전담해서 받아들이는 객체다.)

2) CP는 해당 소켓에서 데이터 입출력이 완료되면 스레드에게 완료 보고를 한다.
    그리고 이 완료 보고를 검사하며 기다리는 워커 스레드를 만든다.

3) 소켓에 입출력 작업이 완료되면 os는 워커 스레드에게 알려준다.

4) 보고 받은 워커 스레드는 입출력 함수를 이용해서 데이터를 처리한다.
    중첩 입출력을 사용하기 때문에 WSARecv, WSASend 함수를 써야 한다.

IOCP 프로그램의 구성

IOCP 프로그램은 기본적으로 2개의 스레드로 구성된다.
메인 스레드는 클라이언트의 연결을 기다렸다가 연결 소켓을 CP에 적용하는 일을,
워커 스레드는 완료 통지를 기다려면서 클라이언트의 데이터를 읽고 처리하는 일을 한다.

2. IOCP 프로그램 개발

CP를 만드려면 CreateIoCompletionPort 함수로 만들 수 있다.

HANDLE WINAPI CreateIoCompletionPort (
  __in HANDLE FileHandle,
  __in_opt HANDLE ExistingCompletionPort,
  __in ULONG_PTR CompletionKey,
  __in DWORD NumberOfconcurrentThreads
);
매개 변수 설명

1) FileHandle : CP에 등록학 파일 핸들이다. 여기에서는 소켓을 사용한다.

2) ExistingCompletionPort : 새로운 CP를 생성하려면 NULL을, 만들어진 CP를 이용하려면 CP의 키 값을 입력한다.

3) CompletionKey : CP를 다루기 위해서 사용하는 CP에 대한 키 값이다.

4) NumberOfconcurrentThreads : CP가 입출력 작업에 얼마나 많은 스레드를 사용할 것인지 설정한다. 명확하지 않으면 0을 설정한다. 0은 os가 자동으로 생성한다.

워커 스레드의 생성

IOCP는 미리 워커 스레드를 만든 다음 입출력이 완료되면 여러 스레드를 중 하나에 작업을 할당한다.
멀티 스레드와 달리 미리 적당한 수의 워커 스레드를 만들어 둔다.
몇 개를 생성할지는 서비스의 종료와 처리 데이터 양에 따라 전적으로 달라진다.
일반적으로 CPU 개수의 2배로 설정한다.

입출력 완료 기다리기와 처리

워커 스레드는 GetQueuedCompletionStatus 함수로 입출력 완료를 기다린다.

BOOL WINAPI GetQueuedCompletionStatus (
  __in HANDLE CompletionPort,
  __out LPDWORD lpNumberOfBytes,
  __out PULONG_PTR lpCompletionKey,
  __out LPOVERLAPPED *lpOverlapped,
  __in DWORD dwMilliseconds
);
매개 변수 설명

1) CompletionPort : CP의 핸들

2) lpNumberOfBytes : 완료된 입출력이 읽고 쓴 데이터의 바이트 크기

3) lpCompletionKey : 유저가 등록한 키값으로 소켓과 소켓을 등록한 CP를 가리킨다.

4) lpOverlapped : OVERLAPPED 구조체를 가리키는 포인터로 완료된 입출력에 대한 정보

5) dwMilliseconds : CP 로 부터 입출력 완료를 기다리는 시간이다. 입출력 완료가 이루어지지 않으면 함수는 실패하고 FALSE를 반환한다.

3. IOCP 기반의 에코 서버 프로그램

소스는 깃허브에 올렸고, 프로그램의 흐름만 적겠다.

1) 중첩 연산 관련 정보를 전달하기 위한 구조체

2) GetQueuedCompletionStatus 함수로 입출력 완료 보고를 기다린다.

3) 만일 읽은 값이 0이라면 소켓을 닫는다.

4) 값을 제대로 읽었다면 WSASend 함수로 데이터를 쓴다. 여기에서는 중첩 속성을 NULL로 했다. 데이터 전송에는 중첩 속성을 사용하지 않아도 된다.

5) 새로 CP를 만든다. 아직 소켓 핸들은 지정하지 않았다.

6) 워커 스레드를 만든다.

7) accept 함수로 새로운 클라이언트 연결을 성공으로 가져왔다면 연결 소켓을 CP에 등록하고, 중첩 입력 연산을 수행한다.

8) 이제 이 소켓에 데이터 입력이 완료되면 os는 CP에 대해서 입출력 완료 보고를 하게 되고, 워커 스레드들 중 하나가 깨어나서 데이터를 처리한다.

소스 코드 받기

뇌자극 TCP_IP 프로그래밍 21강 요약

21강 RAW Socket

1. RAW Socket 의 필요성

setsockeopt 함수를 이용해서 소켓을 세밀하게 조절 할수 있다.
하지만 이 함수는 TCP의 흐름을 제어하는 기술로 더 낮은 계층을 다룰 수는 없다.
이 때 필요한 것이 RAW Socket 다.

로수 소켓을 다루기 위해서는 socket 함수 호출 시 두 번째 매개 변수 값
SOCK_RAW로 설정하면 된다.
로우 소켓은 시스템 하부로 접근하므로 루트 권한이 필요하다.

예제)
int icmp_socket;
icmp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

2. ICMP 체크 프로그램 개발

ICMP란? Internet Control Message Protocl 의 약어다.
ICMP 프로토콜은 다른 호스트나 게이트웨이와 연결된 네트워크 문제가 있는지
확인할 때 주로 쓰인다. 예를 들면 ping 명령어를 들 수 있다.

ICMP는 인터넷 계층의 프로토콜이므로 로우 소켓을 이용해야 한다.

ICMP 프로토콜의 구조

Type :  ICMP 패킷의 유형을 지정하기 위해서 사용한다.
          ICMP 요청은 8이고, 이에 대한 응답은 0이다
code : 각 type에 대한 결과 정보를 전송하기 위해서 사용한다.
         그러므로 type에 따라서 다른 code 값을 가진다.

Checksum : 패킷 송수신 도중에 잡음 등으로 인한 데이터 변조가 발생했는지 검사하기 위해서 사용한다.
동일한 데이터면 동일한 checksum 값이 나오도록 하는 일종의 해시 알고리즘을 사용한다.
checksum 값은 패킷을 보내는 쪽에서 데이터의 내용을 검사해서 만든다.

받는 쪽에서는 동일한 방식으로 데이터 내용을 검사해서 chacksum 값과 일치하는지 확인한다.
만약 전송 도중 변조가 발생했다면 checksum 값이 일치하지 않게 되고
데이터를 받은 측에서는 받은 데이터에 문제가 있음을 인지하게 된다.

ID : 내가 보낸 ICMP 패킷인지 확인하기 위해 사용한다

CMP :  네트워크의 상태를 측정하기 위해 사용한다. 이때 네트워크 속도를 측정하기도 한다.
ICMP 요청에 대한 응답시간을 검사하는 방식이다.
네트워크 속도를 정확하게 측정하려면 여러 ICMP 요청을 보내서 응답 시간을 평균을 내야 한다.
이 경우 ICMP 패킷에 일련번호가 매겨저 있어야 정확한 계산이 가능할 것이다.

3. 패킷 캡처 프로그램 개발

소스는 깃 허브에 올렸고 대략적인 흐름을 적겠다.

1) 로우 소켓을 만든다.
2) 로우 소켓으로 데이터를 읽는다.

3) PrintPacket 함수로 패킷 헤더를 분석하고 프로토콜에 맞는 함수를 호출한다.
4) iphdr 구조체로 IP 헤더를 가리킨다.

5) 프로토콜을 읽어서 여기에 맞도록 처리한다.
6) 패킷 캡처는 헤더를 제외한 사용자 데이터에만 이루어져야 한다.
   TCP 데이터는 IP 패킷 + TCP 패킷 + 데이터로 구성되어 있으므로
   읽은 데이터의 처음부터 IP 패킷 + TCP 패킷 만큼 포인터를 이동해서
   유저 데이터의 시작 지점부터 읽는다.

7) 읽은 데이터를 출력한다.

리눅스용 코드랑 윈도우용 코드를 따로 저장소를 분리해서 리눅스용 깃허브를 먼저 링크 건다.

소스 코드 받기

4. 윈속 RAW Socket 프로그래밍

윈도우는 리눅스와 달리 IP와 ICMP, TCP 패킷 헤더 정보를 저장하기 위한 구조체를 제공하지 않는다.
그러므로 개발자가 필요에 따라서 자체 개발해서 사용해야 한다.
그 외에는 리눅스 용이랑 동일하다.

소스 코드 받기

5. libpcap 을 이용한 패킷 캡처

제대로 된 패킷 프로그램을 만들려면 통계와 필터링 기능을 갖추고 있어야 한다.
libpcap 은 패킷 캡처를 쉽게 할 수 있도록 각종 함수를 제공하는 라이브러리다.

2016년 6월 10일 금요일

뇌자극 TCP_IP 프로그래밍 20강 요약

20강 소켓 옵션

1. 소켓 옵션 변경 함수

현재 설정 값을 가져오는 함수 / 값을 설정하는 함수

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

int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);

s : 설정 값을 가져오거나 설정할 소켓 지정 번호
level : 선택할 수 있는 영역은  SOL_SOCKET, IPPROTO_TCP, NSPROTO_IPX 가 있다.
optname : 변경할 옵션 이름을 가리키는 값이다. level에 따라서 서로 다른 값을 지정할 수 있다.
optval : 해당 옵션의 값을 지정한다.
optlen : 옵션에 따라 optval 은 다양한 크기를 가질 수 있다. optlen으로 optval 값의 크기를 지정한다.

소켓 옵션의 이름(optval)

SOL_SOCKET 영역

SO_REUSEADDR : 소켓 주소 지원을 재사용
SO_SNDBUF : 소켓 쓰기 버퍼의 크기 조정
SO_RCVBUF : 소켓 읽기 버퍼의 크기 조정
SO_LINGER : 소켓 종료 방식의 조정

IPPROTO_TCP 영역

TCP_NODELAY : Nagle 알고리즘의 사용

예제)
int buf_size = 0;
int opt_len;

opt_len = sizeof(int);
//읽기 소켓 버퍼의 크기를 읽어온다.
getsockopt(sockfd, SOL_SOCKET, SO_REVBUF, (void *)&buf_size, &opt_len);

//읽기 소켓 버퍼의 크기를 512 바이트 크기로 설정한다.
buf_size = 512;
setsockopt(sockfd, SOL_SOCKET, SO_REVBUF, (void *)&buf_size, sizeof(int));

소켓 옵션은 루트 권한으로만 변경 가능하다.

2. 소켓 버퍼 설정

소켓 옵션을 이용해서 소켓 버퍼의 크기를 변경할 수 있다.

//읽기 소켓 버퍼의 크기를 1024 바이트 크기로 설정한다.
buf_size = 1024;
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&buf_size, sizeof(int));

슬라이딩 윈도우

TCP는 한번에 전송할 수 있는 최대 세그먼트 크기가 정해져 있다.
이를 TCP MSS(Maximum Segment Size) 라고 한다.
MTU는 한번에 보낼 수 있는 패킷의 크기로 IP 기반의 정보이고
MSS는 TCP 기반의 크기 정보다.

TCP 는 슬라이딩 윈도우라는 기법을 사용한다.
창의 크기를 크게 해서 여러 패킷을 논리적인 하나의 패킷으로 묶어서 처리하는 방식이다.
이 창의 크기를 윈도우 크기라고 한다.

//쓰기 소켓 버퍼의 크기를 2048KB로 설정한다
int buf_size = 2048;
getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (void *)&buf_size, sizeof(int));

일반적으로 윈도우 크기는 기본 설정된 윈도우 크기를 그대로 이용한다.

3. 우아한 연결 종료

TCP는 세션을 맺기 위해서는 세 번의 패킷 교환이 필요하며
이를 Three wasy handshake 라고 한다.
서버와 클라이언트 완전한 통신 종료는 네 번의 패킷 교환이 필요하다
우아한 연결 종료란 네 번의 패킷 교환이 성공적으로 이루어진 상태를 의미한다.

TCP 연결 상태
연결 대기 상태(LISTEN), 정상 연결 상태(ESTABLISHED), 연결 종료 중인 상태(TIME WAIT)

4. 소켓 재사용

소켓은 OS에서 관리하는 자원으로 시스템 전체에 거쳐서 유일해야 한다.
소켓의 유일함은 인터넷 주소와 포트 번호로 결정된다.

소켓 프로그램은 우아한 종료를 위해서 TIME_WAIT 상태에 놓인다
TIME_WAIT 상태로 기다리는 기간은 보통 1~4분 정도인데
이 시간 동안 해당 주소와 포트를 이용할 수 없다.

서버가 TIME_WAIT 상태에 놓여있다면 새롭게 추가 실행하는 서버는
bind 함수의 호출에서 에러가 발생한다.
SO_REUSEADDR 옵션을 이용하면 기존의 소켓 자원을 재사용함으로써,
서버를 바로 실행시킬 수 있다.

예제)
int optval = 1;
listen_fd = socket();
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

5. Nagle 알고리즘

반응 속도와 대역폭 사이에서 최적의 값을 찾아준다.
소켓은 기본적으로 Nagle 알고리즘을 이용해서 데이터를 전송한다.
Nagle 알고리즘은 대역폭을 효과적으로 사용할 수 있도록 도와주지만
반응 속도를 희생 시킨다.
TCP_NODELAY 소켓 옵션을 이용해서 Nagle 알고리즘을 끄거나 킬 수 있다

int iptval = 1;
listen_fd = socket();
setsockopt(listen_fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));

이번 것은 옵션 조정하는 것이기에 소스 코드는 없다.

2016년 6월 9일 목요일

뇌자극 TCP_IP 프로그래밍 19강 요약

19장 RPC

1. RPC에 대해서

네트워크 연결을 담당하는 코드와 코드의 실행을 분리 시켜서 개발 시간을 단축 시킨다.
RPC는 네트워크로 떨어져 있는 컴퓨터에서 코드를 실행하는 방식으로 작동한다.
클라이언트에서 코드를 실행시켜 줄 것을 요청하면,
서버는 해당 코드를 실행해서 그 결과를 되돌려준다.

2. RPC 작동 방식

독립된 프로그램의 실행을 요청하는 방식으로 작동한다.
실행 위치는 로컬과 원격 모두를 지원한다.
RPC는 외부 프로그램을 실행시키는 개념이므로
프로그램 이름과 프로그램 실행 인자가 필요하다.
RPC를 이용한 네트워크 프로그램은 실행인자와 실행할 코드를 정확히 명시해야 한다.
이들은 XDR이라는 표준화된 양식에 따라 만들면 된다.

XDR 코드와 서버에서 실행할 코드만 개발하면 된다.

3. RPC의 작동 환경 구축

RPC 관련 패키지를 설치해야 한다.
sudo apt-getinstall portmap

설치하면 portmap 라는 프로그램을 확인할 수 있다
ps -ef | grep portmap 

나 같은 경우는 우분투를 사용하는데 이미 깔려있었다;;

4. RPC 프로그래밍

클라이언트의 요청을 처리할 코드의 이름과 코드가 처리할 데이터의 형식을 정의한다.
타입은 XDR 이라는 특수한 형태를 따른다.

XDR 필터 생성

파일 이름은 파일이름.x 으로 만들면 된다. 주석은 /**/만 쓸 수 있다.

예제)

const MAXAVGSIZE = 200;

struct input_data {
  double input_data<MAXAVGSIZE>;
};

typedef struct input_data input_data;

/*
프로그램의 이름은 AVERAGE 이고,
프로그램의 이름을 가리키는 번호는 3901을 사용했다.
프로그램의 버전은 1이다.
*/
program AVERAGE {
  version AVERAGEVERS {
      double AVERAGE(input_data) = 1;
  } = 1;
} = 3901;

위를 컴파일 해야 한다.

rpcgen avg.x

그럼 4개 파일이 생성된다.

avg.h : avg.x를 해석해서 만들어진 각종 함수와 값들이 선언되어 있다.

avg_clnt.c : avg.x를 해석해서 만들어진 클라이언트 프로그램이다.
               여기에 코드를 추가하는 형식으로 클라이언트를 만든다.

avg_svc.c : 서버에서 호출할 callee프로그램이다.
               여기에 코드를 추가해서 프로그램을 완성한다.

avg_xdr.c : 서버와 클라이언트에서 사용할 XDR 코드가 들어있다.

코드는 깃허브에 올려놨다.

코드 다운 받기

뇌자극 TCP_IP 프로그래밍 18강 요약

18강 윈도우 고급 네트워크 프로그래밍

1. 윈속 확장 함수

모든 윈속 확장 함수는 WSA로 시작하며 기본적인 소켓 함수들에 대한 확장 함수도 제공한다

socket 함수에 대응되는 윈속 확장 함수인 WSASocket 함수다.

SOCKET WSASocket (
  __in intaf,
  __in int type,
  __in int protocol,
  __in LPWSAPROTOCOL_INFO lpProtocolInfo,
  __in GROUP g,
  __in DWORDdwFlags
);

2. WSAEventSelect 네트워크 프로그램 개발

이벤트 객체와 select 함수를 이용한 비동기 입출력 모델이다.

이벤트 객채 생성

WSAECENT WSACreateEvent(void)l

이벤트 객체를 소켓과 묶어주기

이벤트 객체가 작동하려면 이벤트를 기다릴 소켓과 이벤트 종류를 지정해 줘야 한다.

int WSAEventSelect(
  __in SOCKET s,
  __in WSAEVENT hEventObject,
  __in long lNetworkEvents
);

s : 이벤트 객체와 묶어줄 소켓 지정번호
hEventObject : 소켓 s와 묶어줄 이벤트 객체
lNetworkEvents : 이벤트 객체를 신호 상태로 만들기 위해서 기다릴 이벤트 목록

이벤트 값

FD_READ : 입력 이벤트
FD_WRITE : 출력 이벤트
FD_OOB : 긴급 데이터(OOB) 이벤트
FD_ACCEPT : 클라이언트 연결 요청 이벤트
FD_CONNECT : 연결 완료 이벤트
FD_CLOSE : 연결 종료 이벤트

비트 연산으로 하나의 소켓에 여러 이벤트를 발생하도록 지정할 수 있다.
WSAEventSelect(s, hEventObject, FD_READ | FD_WRITE);

이벤트 기다리기

DWORD WSAWaitForMultipleEvents(
  __in DWORD cEvents,
  __in const WSAEVENT *lphEvents,
  __in BOOL fWaitAll,
  __in DWORD dwTimeout,
  __in BOOL fAlertable
);

cEvents : lphEvent의 배열의 크기로 이벤트를 기다릴 이벤트 객체 핸들의 개수다.
lphEvents : 이벤트 객체 핸들 배열을 가리키는 포인터다.
fWaitAll : 기다림의 방식을 지정한다.
   true 라면 모든 이벤트 객체가 신호 상태가 되어야 반환한다.
   false라면 핸들 중 하나라도 신호 상태가 되면 반환한다.
   일반적으로 false를 사용한다.
dwTimeout : 신호 상태를 기다리는 제한 시간이다. (WSA_INFINITE를 설정하면 무한정 기다린다.)
fAlertable : true로 설정하면 이벤트 객체를 가지고 작업할 스레드가 반응할 수 있는 상태에 놓일 때까지 호출되지 않게 된다.

이벤트 종류 알아내기

int WSAEnumNetworkEvents(
  __in SOCKET s,
  __in WSAEVENT hEventObject,
  __out LPWSANETWORKEVENTS lpNetworkEvents
);

s : 이벤트가 발생한 소켓 지정번호
hEventObject : 이벤트 객체를 리셋하기 위해서 사용한다.
lpNetworkEvents : 이벤트의 종류 값을 가지고 있는  WSANETWORKEVENTS 구조체의 포인터다. 이 구조체는 다음과 같이 정의되어 있다.

typedef struct _WSANETWORKEVENTS {
  long lNetworkEvents;
  int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS , *LPWSANETWORKEVENTS;

lNetworkEvents : 이벤트의 종류를 확인할 수 있다.
iErrorCode : 에러 코드를 저장한다.

3. WSAAsyncEvent 네트워크 프로그램 개발

윈도우 메시지 형태로 네트워크 이벤트를 처리한다.

1) 소켓을 위한 윈도우와 처리할 네트워크 이벤트를 등록한다.
2) 소켓에 등록한 이벤트가 발생하면 관련 정보가 메시지 큐에 들어간다.
   이 정보에는 이벤트가 발생한 소켓 지정 번호가 포함된다.
3) 윈도우 프로시저가 호출되고, 이벤트 종류에 따라서 처리하면 된다.

윈도우 객체와 메시지 처리

윈도우 메시지를 사용하므로 반드시 윈도우 객체가 있어야 한다.

HWND CreateWindow(
  __in LPCTSTR lpClassName,
  __in LPCTSTR lpWindowName,
  __in DWORD dwStyle,
  __in int x,
  __in int y,
  __in int nWidth,
  __in int nHeight,
  __in HWND hWndParent,
  __in HMENU hMenu,
  __in HINSTANCE hInstance,
  __in LPVOID lpParam
);

lpClassName : 등록된 윈도우 클래스 이름
lpWindowName : 윈도우 타이틀에 표시될 이름
dwStyle : 윈도우 스타일
x : 수평 좌표
y : 수직 좌표
nWidth : 윈도우 넓이
nHeight : 윈도우 높이
hWndParent : 윈도우를 소유할 부모 윈도우의 핸들러
hMenu : 윈도우에서 사용할 메뉴의 핸들러
hInstance : 윈도우와 연결될 인스턴스 핸들러
lpParam : 메시지 정보

소켓 메시지를 처리할 윈도우 객체 지정

int WSAAsyncSelect(
  __in SOCKET s,
  __in HWND hWnd,
  __in unsigned int wMsg,
  __in long lEvent
);

s : 이벤트를 기다릴 소켓을 가리키는 소켓 지정번호
hWnd : 이벤트가 발생했을 때 메시지를 처리할 윈도우 객체 핸들러
wMsg : 발생한 소켓 이벤트를 가리키는 번호
lEvent :  기다릴 소켓 이벤트의 종류

이벤트 종류

FD_READ : 입력 이벤트
FD_WRITE : 출력 이벤트
FD_OOB : 긴급 데이터(OOB) 이벤트
FD_ACCEPT : 클라이언트 연결 요청 이벤트
FD_CONNECT : 연결 완료 이벤트
FD_CLOSE : 연결 종료 이벤트

4. 중첩 입출력 모델

윈도우는 멀티 태스킹을 지원하므로 파일 입출력도 동시에 다룰 수 있다.
동시에 여러 개를 입력하면 데이터가 중첩되는 영역이 생긴다.
중첩 입출력 모델은 데이터가 중첩되며 동시에 처리할 수 있다.

중첩 소켓 만들기

SOCKET WSASocket (
  __in int af,
  __in int type,
  __in int protocol,
  __in LPWSAPROTOCOL_INFO lpProtocolInfo,
  __in GROUP g,
  __in DWORD dwFlags
);

lpProtocolInfo : 소켓의 특성을 정의한 LPWSAPROTOCOL_INFO 구조체의 포인터
g : 소켓 그룹의 식별자
dwFlags : 소켓 속성을 지정

중첩 소켓을 이용한 데이터 처리

int WSAARecv (
  __in SOCKET s
  __inout LPWSABUF lpBuffers,
  __in DWORD dwBufferCount,
  __out LPDWORD lpNumberOfBytesRecvd,
  __inout LPDWORD lpFlags,
  __in LPWSAOVERLAPPED lpOverlapped,
  __in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

s : 데이터를 쓸 소켓 지정 번호
lpBuffers : 전송할 데이터의 정보를 담고 있는 WSABUF 구조체의 배열을 가리키는 포인터
dwBufferCount : lpUbffers의 배열의 크기다.
lpFlags : recv 함수의 flags와 같은 일을 한다.

소스 코드 받기