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와 같은 일을 한다.

소스 코드 받기

2016년 5월 29일 일요일

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

1.  epoll 소개

epoll은 event + poll의 합성어다. 대용량의 데이터를 처리하기 위해서 입출력 다중화 기술의 장점을 계승하며, 단점을 보완했다.
입출력 다중화의 결정적인 문제는 비트 테이블을 순환하면서 어떤 파일에 데이터 변화가 있는지 확인해야 한다는 것이였다.

epoll은 입출력 다중화 처럼 관심 있는 파일 목록을 poll에 유지한다.
poll에 있는 파일에 데이터 변화가 생기면 이벤트가 발생하고
이벤트를 받은 프로그램은 poll에서 이벤트가 발생한 파일 목록을 가져올 수 있다.
이 poll은 파일의 데이터 변화를 이벤트로 관리하므로 이벤트 풀이라고 한다.

2. epoll 사용법

1) 적당한 크기의 이벤트 풀을 만든다.
2) 이벤트를 관리해야 하는 파일이 들어오면 이벤트 풀에 집어 넣는다.
3) 이벤트 풀에서 이벤트가 발생하기를 기다린다.
4) 이벤트가 발생하면, 이벤트가 발생한 파일의 목록을 가져오고 읽기/쓰기 작업을 한다.

이벤트 풀의 생성

#include <sys/epoll.h>
int epoll_create(int size);

size : 이벤트 풀의 크기

이벤트 풀 관리

이벤트 풀의 관리할 파일을 넣고 빼는 작업

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

-efpd : epoll_create 함수로 만든 epoll 기술자다
-op : 이벤트 풀의 관리 명령을 지정한다.
   EPOLL_CTL_ADD : 관리하고자 하는 파일 지정 번호 fd를 efpd가 가리키는 이벤트 풀에 추가한다. 추가된 파일에 이벤트가 발생하면 이벤트 풀에 전달된다.
   EPOLL_CTL_MOD : 지정한 FD에 대해서 event 의 정보를 변환한다.
   EPOLL_CTL_DEL : 파일 지정 번호 fd를 이벤트 풀에서 제거한다.
-event : 검사할 이벤트 종류를 지정하기 위해서 사용하는 구조체
   EPOLLIN  : 입력이벤트를 검사한다.
   EPOLLOUT : 출력 이벤트를 검사한다.
   EPOLLERR : 에러 이벤트를 검사한다.
   EPOLLHUP : 데이터 송수신 지체 이벤트를 검사한다.

이벤트 관리

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

-epfd : epoll_create 함수로 만든 이벤트 풀의 지정번호
-event : 이벤트가 발생한 파일의 정보는 epoll_event 구조체로 넘어온다.
-maxevetns : 한번에 가져올 수 있는 이벤트의 크기를 지정한다. 0보다 커야 하고, events 배열의 크기를 초과하면 안된다.
-timeout : 기다릴 시간을 밀리 초 단위로 지정한다. -1을 지정하면 이벤트가 발생할 때까지 무한정 기다리고, 0을 지정하면 이벤트를 기다리지 않고 즉시 반환한다.

3. epoll 네트워크 프로그래밍

프로그램의 흐름

1) socket() -> bind() -> listen() 순서로 듣기 소켓을 만든다.
2) epoll_ctl 함수로 듣기 소켓을 이벤트 풀에 추가한다.
3) epoll_wait 함수로 이벤트를 기다린다.
4) 발생한 이벤트를 검사한다.
5) 만약 듣기 소켓에서 이벤트가 발생하면
   (1) accept 함수를 호출해서 연결 소켓을 만든다.
   (2) epoll_ctl 함수로 연결 소켓을 이벤트 풀에 추가한다.
   (3) 3)으로 돌아간다.
6)만약 연결 소켓에서 이벤트가 발생했다면
   (1) read/write 함수로 클라이언트와 통신한다.
   (2) 클라이언트 종료 이벤트라면 이벤트 풀에서 연결 소켓을 제거한다.
   (3) 3)으로 돌아간다.

4. epoll의 장점과 단점

장점
- 멀티 스레드, 멀티 프로세스 방식에 비해서 매우 효율적으로 작동한다.
- 데이터 변경이 있는 파일을 찾기 위해 루프를 돌 필요가 없다.

단점
- 데이터 처리에 오랜 시간이 걸리는 서비스에는 적당하지 않다.

5. 리얼 타임 시그널로 대용량 데이터 처리하기

시그널의 특징

-시그널은 대기열이 없다 : 대기열이 없기에 한 번에 2개 이상의 시그널이 입력되면 뒤에 도착한 시그널은 잃어 버리게 된다.

- 시그널은 부가 정보를 가지지 않는다 : 시그널은 의미만 알려주고 어떤 정보도 포함하지 않는다. 어떤 파일에서 어떤 종류의 이벤트가 발생했는지의 정보도 필요하다.

리얼 타임 시그널

시그널의 단점을 개선한 기법이다. 다음과 같은 특징이 있다.

-대기열을 가진다 : 시그널을 저장하기 위한 대기얼을 가지고 있다. 두 개 이상의 시그널이 동시에 도착해도 잃어버리지 않고 전달된다.

-부가 정보를 가진다 : 시그널 번호, 시그널이 발생한 파일 지정 번호, 시그널이 발생한 이유, 프로세스 ID, 유저 ID 등 다양한 정보를 함께 전달한다.

리얼 타임이 사용하는 siginfo 구조체의 모습이다.

typedef struct siginfo {
  int si_sinno;
  int si_errno;
  int si_code;
  pid_t si_pid;
  uid_t si_uid;
  void *si_addr;
  uinon sigval si_value;
  union{
    struct {
      int _band;
      int _fd;
    } _sigpoll;
  }_sifields;
}  siginfo_t;

리얼 타임 시그널 대기

#include <signal.h>
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec timeout);

-sigset_t : 이들 함수는 sigset_t 에 설정된 시그널을 가진다.
-siginfo_t : 시그널이 전달되면 siginfo_t 구조체의 포인터를 반환한다. 여기에는 리얼 타임 시그널과 관련된 정보를 포함하고 있다.
-timeout : sigtimewait 함수는 timeout에 지정된 시간만큼 기다린다. 만약 timeout 시간 안에 시간이 없으면 바로 반환한다.

6. 리얼 타임 시그널과 소켓 프로그래밍

프로그램의 흐름

1) sigemptyset() -> sigaddset() -> sigprocmask() 로 리얼 타임 시그널을 설정한다.
2) socket() -> bind() -> listen() 으로 흐름은 같다.
3) 듣기 소켓을 비봉쇄/비동기 소켓으로 만든다.
4) 듣기 소켓이 리얼 타임 시그널을 발생하도록 설정한다.
5) sigwaitinfo 함수로 소켓 이벤트를 기다린다.
6) 만약 듣기 소켓에서 발생한 이벤트라면
   (1) accept 함수를 호출해서 연결 소켓을 만든다.
   (2) 소켓을 비봉쇄/비동기 소켓으로 만들어서 리얼 타임 시그널을 발생하도록 설정한다.
7) 만약 연결 소켓에서 발생한 이벤트라면 소켓 데이터를 읽어서 처리한다.

소스 코드를 보고 해보는게 이해가 가장 빠르다

소스 코드 받기

2016년 5월 28일 토요일

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

1. 리눅스 입출력 모델

입출력 모델은 동기, 비동기, 봉쇄, 비봉쇄의 조합이다.
입출력 모델은 입력/출력 모두에 대응되는 모델이지만 서버와 클라이언트 프로그램
모두 입력 함수가 중요하게 다루어지니 read 함수를 기준으로 설명한다.

봉쇄와 비봉쇄, 동기와 비동기

봉쇄 -  프로그램이 특정 영역에서 봉쇄되어서 봉쇄가 풀리기를 기다린다.
비봉쇄 - 기다리지 않으면 비봉쇄다.

동기 - 사건의 시간을 기다리면서 서로 맞추어 알 수 있는 상태, 이것을 동기라고 한다.
비동기 - 사건의 시간을 서로 맞추지 않은 상태다.

동기/봉쇄 입출력 모델

1) 응용 프로그램은 read 함수를 호출한다.
2) 요청을 받은 커널은 데이터를 읽기 위한 초기 작업을 수행한다.
3) 데이터가 준비될 때까지 응용프로그램은 대기한다.
4) 데이터가 들어온다.
5) 커널은 데이터를 유저 영역으로복사하고 read 함수는 반환한다
6) 응용 프로그램은 봉쇄가 풀리고 다음 작업을 수행한다.

장점 - 프로그램 흐름이 명확해서 이해하기 쉽고, 결과 예측과 디버깅이 매우 쉽다.
단점 - 입출력을 기다리는 동안 다른 일을 할 수 없어서 둘 이상의 소켓을 다룰수 없다.

동기/비봉쇄 입출력 모델

비봉쇄 모델에는 비봉쇄 소켓이 필요하다. 비봉쇄 소켓에 read 함수를 호출하면,
커널은 데이터를 읽을 준비만 하고 즉시 반환한다.
fcntl 함수를 이용하면 봉쇄 소켓을 비봉쇄 소켓으로 만들 수 있다.

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, long arg);

-fd : 특성을 제어할 파일 지정 번호
-cmd : 제어에 사용할 명령으로 현재 파일의 설정 값을 가져오거나 설정 값을 변경하는 등의 일을 할 수 있다. 'F_GETFL'로 현재 설정 값을 가져오고, 'F_SETFL'로 현재 설정 값을 변경할 수 있다.
-arg : 변경하기 원하는 설정 값을 지정한다. O_NONBLOCK 으로 비봉쇄로 지정할 수 있다.

비봉쇄 소켓은 데이터가 없어도 즉시 반환하는데, 이때 반환 값이 -1 이다.
-1을 비봉쇄 상태에서 즉시 반환 한 것을 에러라고 인식하면 문제가 되기에 에러 처리를 해줘야 한다.

실제 에러인지, 비봉쇄에 의한 반환인지 확인 방법은 errno 값을 확인 해야 한다.
errno 값이 EAGAIN 또는 EWOULDBLOCK 이면 비봉쇄에 의한 반환이다.
데이터가 없어서 반환 했음을 의미하니 에러로 처리하면 안된다.

#include <errno.h>
extern int errno;

1) read 함수를 호출하면 커널은 데이터를 읽을 준비를 하고 바로 반환한다.
2) 읽을 데이터가 없으면 errno를 EAGAIN 으로 설정하고 -1을 반환한다.
3) 2)를 계속 반환한다.
4) 드디어 파일에 데이터가 입력되었다.
5) read 함수를 호출하면, 커널은 읽은 데이터를 유저 영역으로 복사한다.

장점 - 바쁜 대기 때문에 발생하는 문제는 어느 정도 해결 가능하다.
단점 - 의도하지 않은 지연 시간과 높은 CPU 점유율 때문에 거의 사용하지 않는다.

비동기/봉쇄 입출력 모델

1) 입력 함수를 호출하기 전에 select 함수를 호출한다. 1,2,3 번 소켓의 데이터 입력을 검사하도록 했다.
2) select 함수가 호출되면 1,2,3 번 소켓에 대해서 데이터 입력 초기화를 하고, 데이터 입력을 기다린다.
3) 1번과 2번 소켓에 데이터가 입력되면 반환한다
4) 1)번 소켓에 대해서 read 함수를 호출한다.
5) read 함수는 봉쇄 모드이지만 이미 읽을 데티어가 읽으므로 데이터를 읽어오고 반환한다.
6) 데이터를 처리한다.
7) 4번으로 가서 2번 소켓에 대해서 read 함수를 호출한다.

장점 - 바쁜 대기 상태에 놓이지 않고서도 하나의 프로세스로 여러개의 소켓을 처리 가능
단점 - 모델을 구현한 방식이 문제다(fd_set의 복사와 비트 테이블을 모두 검사 해야 하는 비효율)

비동기/비봉쇄 입출력 모델

1) read 함수를 호출하면 운영체제는 데이터를 읽을 준비를 한다.
2) 데이터를 읽기 위한 초기 작업을 수행한다.
3) 다른 작업을 할 수 있다.
4) 데티어가 준비되면 커널은 이벤트를 발생한다. 리눅스에서는 시그널을 발생한다. (윈도우는 이벤트 객체를 이용한다.)
5) 프로그램이 read 함수를 호출하면 데이터를 커널 모드에서 유저 모드로 복사한다.
6) 시그널 핸들러를 호출해서 데이터를 처리한다.(윈도우눈 콜백 함수를 호출한다.)

리눅스는 epoll가 윈도우는 IOCP가 이 모델을 따른다.

2. 윈도우 입출력 모델

동기/봉쇄 모델

소켓은 기본적으로 동기/봉쇄 모델로 만들어진다. 리눅스와 차이가 없다.

동기/비봉쇄 모델

ioctlsocket 함수로 소켓을 비봉쇄로 만든다.

int ioctlsocket(
  __in SOCKET s,
  __in long cmd,
  __inout u_long *argp
);

s : 설정을 변경할 소켓 핸들이다.
cmd : 소켓의 설정 명령이다.
argp : 소켓의 설정 값으로 cmd의 값을 argp로 변경한다.

소켓을 비봉쇄로 변경하기 위해서는 cmd 매개변수의 FIONBIO를 1로 설정하면 된다.
0으로 하면 봉쇄, 1로 하면 비봉쇄이다.

3. 모델 선택

모델을 선택하기 위해서는 프로그램의 용도와 그에 따른 규모가 결정돼야 한다.

리눅스에서의 일반적인 선택 기준

1) 동접 클라이언트가 많지 않고 연결과 종료가 빈번하지 않은 서비스, 데이터 처리에 시간이 꽤 걸리는 서비스, FTP와 같은 파일 전송 같은 서비스는 동기/봉쇄 모델로 개발한다.

2) 많은 동접 클라이언트가 예상되고, 데이터 처리 시간이 오래 걸리지 않는 서비스라면 비동기/봉쇄 모델의 기술을 사용해서 개발한다.

윈도우에서의 일반적인 선택 기준

1) 많은 수의 클라이언트가 예상되지 않는다면 동기/봉쇄 모델에 멀티 스레드 기술을 응용해서 개발한다.

2) 많은 수의 클라이언트 처리가 예상된다며 Overlapped I/O 와 IOCP를 사용한다.

2016년 5월 25일 수요일

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

1. 윈도우 멀티 스레드 기술 소개

멀티 스레드 기술은 os 종속 기술이 아니기에 기본적으로는 리눅스 멀티 스레드와 개념은 같다. 단지 사용범위와 스레드 API가 다를 뿐이다.

리눅스는 주로 멀티 프로세스를 선호하고
윈도우는 주로 멀티 스레드를 선호한다.

2. 윈도우 스레드 프로그래밍

윈도우와 리눅스와 별 다르지 않다. 각 과정에서 사용되는 함수의 이름과 매개 변수가 차이가 있을 뿐이다.

윈도우 스레드에 대한 이해

커널 객체 - 커널에서 생성된 여러 자원(세마포어, 뮤텍스, 이벤트, 파이프)등을 의미한다
커널 객체는 직접 접근하지 못하고 대신에 핸들을 이용해서 제어한다.

커널 객체는 신호 상태와 비신호 상태를 가질 수 있으며 이 신호 상태를 검사해서 상태를 확인 할 수 있다. 일종의 시그널 시스템이다.

스레드 생성

HANDLE WINAPI CreateThread(
  __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in         SIZE_T dwStackSize,
  __in         LPTHREAD_START_ROUTINE lpStartAddress,
  __in_opt   LPVOID lpParameter,
  __in         DWORD dwCreationFlags,
  __out_opt  LPDWORD lpThreadId
);

lpThreadAttributes : NULL이면 기본 보안 특성을 사용한다.
dwStackSize : 스레드는 자신만의 스텍 공간을 가지는데 스텍 공간을 지정할 수 있다.
lpStartAddress : 워커 스레드가 실행할 코드를 가지고 있는 스레드 함수다
lpParameter : 스레드 함수에 넘길 매개변수다.
dwCreationFlags : 스레드가 만들어질 때 즉시 실행할지 아니면 대기할지 정의 한다.
 0이면 스레드는 즉시 실행한다. CREATE_SUSPENDED 플래그를 설정하면 ResumThread 함수를 호출 할 때까지 대기한다. 주로 디버깅 용도로 사용한다.
lpThreadId : 스레드를 식별하기 위한 식별 번호다. 사용하지 않으려면 NULL을 지정한다.

스레드 종료

BOOL WINAPI CloseHandle(
  __in HANDLE hObject
);

스레드 종료 대기

이 함수들은 핸들을 매개변수로 받기 때문에 스레드 객체 뿐만 아니라, 핸들로 제어되는 모든 종류의 객체를 기다리기 위해서 사용할 수 있다.

DWORD WINAPI WaitForMultupleObjects(
  __in DWORD nCount,
  __in const HANDLE *lpHandles,
  __in BOOL bWaitAll,
  __in DWORD dwMilliseconds
);

nCount : 여러 객체의 시그널 상태를 기다릴 때 몇 개의 객체를 기다릴지 정할 수 있다.
lpHandles : 기다릴 핸들의 배열
bWaitAll : TRUE 면 배열에 있는 모든 핸들이 시그널 상태가 되어야 반환한다.
dwMilliseconds : 밀리 초 단위로 기다리는 시간에 제한을 둘 수 있다. 0을 지정하면 바로 반환된다. INFINITE로 하면 모든 핸들이 시그널 상태가 되어야 반환한다.

단지 한 객체의 시그널 상태를 기다리는 것을 제외하면  WaitForMultupleObjects 와 같다.

DWORD WINAPI WaitForSingleObjects(
  __in HANDLE hHandle,
  __in DWORD dwMilliseconds
);

hHandle : 시그널 상태를 가디리가 위한 핸들
dwMilliseconds : 기다리기 위한 제한 시간

이들 함수로 스레드 핸들이 시그널 상태에 놓이길 기다린다.
시그널 상태라는 것은 스레드가 종료되어 핸들의 상태에 변화가 생겼음을 의미한다.
모든 핸들러가 시그널 상태에 놓여서 함수가 반환되면 그때 스레드 핸들을 제거하면 된다.

3. 뮤텍스를 이용한 윈도우 스레드의 동기화

스레드간 자원 공유를 위해 접근 제어를 꼭 해줘야 하며, 이때 리눅스와 마찬가지로 임계영역을 만들어 스레드의 진입을 제어한다.

유저 모드 동기화

윈도우는 임계 영역 객체를 이영해서 관리한다.
임계 영역 객체를 초기화하고, 임계 영역을 진입하고, 떠나고, 
더 사용하지 않는 임계 영역 객체는 해제하면 된다.

임계영역 초기화

void WINAPI InitializeCriticalSection(
  __out LPCRITICAL_SECTION lpCriticalSection
);

임계 영역 진입과 떠나기

void WINAPI EnterCriticalSection(
  __inout LPCRITICAL_SECTION lpCriticalSection
);

void WINAPI LeaveCriticalSection(
  __inout LPCRITICAL_SECTION lpCriticalSection
);

임계 영역 객체는 유저 모드에서 작동하는 동기화 도구다.
커널 모드 객체를 다루기 위해서는 유저모드 -> 커널모드-> 유저모드 로의 
모드 전환이 일어나는데 유저 모드에서 작동하는 임계 영역 객체는 
모드 전환이 이루어지지 않으므로 커널 코드에서 작동하는 다른 객체보다 
더 빠르게 작동한다.

커널 모드 동기화

HANDLE WINAPI CreatMutex(
  __in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes,
  __in BOOL bInitalOwner,
  __in_opt LPCTSTR lpName
);

lpMutexAttributes : NULL이면 기본 특성이 사용되며 자식 프로세스에 상속할 수 없다.
bInitalOwner : 뮤텍스를 생성한 스레드에 뮤텍스 소유권을 제공할지 여부를 결정한다. 
TRUE를 지정하면 뮤텍스의 소유권은 작성 스레드에게 넘어가고, 소유권은 비 신호 상태가 된다.
FALSE를 지정하면 소유권을 요구하지 않게 된다.
lpName : 뮤텍스는 전역 커널 객체로서 다른 프로세스와 함께 사용할 수 있다. 다른 프로세스와 함께 사용하기 위해서는 뮤텍스의 고유 이름을 알고 있어야 한다. 뮤텍스에 이름을 지어 준다.

임계 영역 신호 상태

뮤텍스를 얻은 스레드가 작업을 마치면 함수를 호출하여 임계영역을 빠져나가게 된다.
이 함수를 호출하면 뮤텍스 객체는 신호 상태가 되고 다른 스레드가 뮤텍스를 얻을 수 있게 된다.

BOOL WINAPI ReleaseMutex(
  __in HANDLE hMutex
);

4. 조건 변수를 이용한 스레드 동기화

조건 변수 역시 스레드 동기화를 위해서 사용한다.
조건 변수를 이용하면 일정한 조건을 만족할 때까지 스레드를 기다리게 할 수 있다.

조건 변수의 생성과 초기화

VOID WINAPI InitalizeConditionVariable (
  __out PCONDITION_VARIABLE ConditionVariable
);

ConditionVariable : 초기화할 조건 변수

조건 변수로부터 신호 대기

BOOL WINAPI SleepConditionVariableCS (
  __inout PCONDITION_VARIABLE ConditionVariable,
  __inout PCRITICAL_SECTION CriticalSection,
  __in DWORD dwMilliseconds
);

ConditionVariable : 신호를 기다릴 조건 변수
CriticalSection : 조건 변수의 진입을 제어하기 위해서 뮤텍스 객체를 사용한다.
dwMilliseconds : 신호를 기다릴 제한 시간을 밀리 초 단위로 설정한다. 0이면 바로 반환,
무한정 기다리게 하려면 INFINITE를 설정

조건 변수에 신호 전송

신호 대기 -> 신호 전달 -> 깨움 의 매커니즘을 가진다.

//하나의 대기 중인 스레드를 깨울 때
VOID WINAPI WakeConditionVariable(
  __inout PCONDITION_VARIABLE ConditionVariable
);

//모든 대기 중인 스레드를 깨울 때
VOID WINAPI WakeAllConditionVariable(
  __inout PCONDITION_VARIABLE ConditionVariable
);

5. 멀티 스레드 기반의 소켓 프로그램 제작

멀티 스레드 소켓 프로그램의 흐름

1) socket() -> bind() -> listen() -> accept() 로 이루어지는 흐름은 pthread와 같다.
2) accept 함수가 성공적으로 호출되어서 연결 소켓이 만들어지면, CreatThread 함수로 새로운 스레드를 만든다. 스레드 함수의 매개변수로 연결 소켓이 전달된다.
3) 메인 스레드는 즉시 accept 함수를 호출해야 하기 때문에 스레드의 종료를 기다리지 않고 (CloseHandle 함수로) 핸들을 닫는다.

위의 내용은 소스 코드를 보고 익히자
역시 깃허브에 소스 코드를 올렸다.

2016년 5월 24일 화요일

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

1. 멀티 스레드 프로그래밍

운영체제의 최소 실행단위는 프로세스다.
멀티 태스킹이 프로세스를 스위칭 하는 방식이라면
멀티 스레드는 하나의 프로세스가 둘 이상의 코드를 서로 스위칭 하는 방식이다.

프로세스 코드의 일부를 문맥이라고 하고,
이 문맥을 포함한 프로세스의 흐름을 스레드라고 한다.

멀티 스레드란?
1) 두 개 이상의(문맥을 포함한) 스레드를 만들어서
2) 이 문맥을 스위칭함으로써
3) 멀티 태스킹 하는 기법

스레드는 동일한 프로세스에서 실행되므로 프로세스의 자원을 서로 공유할 수 있다.

2. 멀티 스레드 프로그램 개발

멀티 스레드 프로그램 흐름

1) 분기 시점에 프로세스가 fork 함수로 자식 프로세스를 생성하면
    스레드는 스레드 생성 함수로 새로운 스레드를 생성한다.
2) 원본 프로세스를 부모 프로세스라고 하고,
    새로 만든 프로세스를 자식 프로세스라고 한다
3) 원본 스레드를 메인 스레드라고 하고, 새로 만든 스레드를 워커 스레드라고 한다

4) fork 함수로 만들어진 프로세스는 독립적으로 주어진 일을 수행한다.
    스레드 생성 함수로 만들어진 스레드도 독립적으로 작업을 수행한다.
5) 부모 프로세스는 wait 함수로 자식 프로세스의 종료를 기다린다.
6) 메인 스레드는 스레드 대기 함수로 워커 스레드를 기다린다.

스레드 생성
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread attr_t *attr,
                            void* (*start_routine)(void *), void *arg);

thread : 스레드가 만들어지면 스레드를 가리키는 스레드 식별번호가 포인터로 반환된다.
attr : 만들어지는 스레드의 특성을 정의하기 위해 사용한다. 기본 특성은 NULL 이다.
start_routine : 만들고자 하는 스레드 함수의 포인터이다.
arg : 스레드 함수가 실행될 때 넘어갈 매개변수다.

스레드 대기 
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);

th : 기다리고자 하는 스레드의 식별 번호다.
thread_return : 스레드의 종료 값을 가져온다.

스레드 분리
int pthread_detach(pthread_t th);

th : pthread_create 함수로 만든 워크 스레드의 식별번호로 ,
     th를 식별번호로 하는 워크 스레드를 분리 시킨다.

예제는 깃허브에 올렸다.
단, 주의할 점은 컴파일 할 때 pthread 라이브러리를 링크 시켜줘야 한다.

3. 멀티 스레드와 소켓 프로그래밍의 결합

멀티 스레드의 개념이 멀티 프로세스와 별반 다르지 않으므로 쉽게 응용할 수 있다.
멀티 프로세스 기반 소켓 프로그래밍의 호름을 그대로 따르면서
fork 함수와 wait 함수 대신 스레드 함수를 쓰면 된다.
소켓에 멀티 스레드 기술을 접못하는 이유는 다수 클라이언트를 처리하기 위해서다.

fork 함수 대신 pthread_create 함수를, wait함수 대신 pthread_join 함수를 사용한다.
두 개 이상의 워커 스레드를 다루려면 pthread_detach 함수로
워커 스레드의 종료를 기다리지 않다록 해야 한다.

4. 스레드 간 공유 자원 보호

멀티 스레드에서는 공유 자원을 보로하기 위해 뮤텍스를 사용한다.
단, 뮤텍스는 단일 프로세스에만 사용할 수 있다는 한계가 있다.
서로 다른 프로세스 간에 공유 자원을 보호할 목적이면 세마포어를 사용해야 한다.

뮤텍스 생성
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr *attr);

mutex : 초기화시킬 뮤텍스 객체
attr : 뮤텍스 객체를 attr 값으로 초기화 기본 설정을 사용하려면 NULL 을 이용한다.

뮤텍스 잠금
int pthread_mutex_lock(pthread_mutex_t *mutex);

mutex : 잠금에 사용할 뮤텍스 객체다.

뮤텍스 잠금 해제
int pthread_mutex_unlock(pthread_mutex_t *mutex);

mutex : 잠금 해제에 사용할 뮤텍스 객체다.

스레드 대기
여러 객체가 함께 움직이려고 하면 신호를 기다려야 일이 제대로 진행되는 때가 있다.
병렬 처리 프로그램이 대표적인 예이다.
여러 스레드를 동시에 실행 시켜야 할 때 조건 변수를 사용한다.

조건 변수 

조건 변수 객체 만들기
int pthread_cond_init(pthread_cond_t *cond, const pthread_cond_attr *attr);

cond : 조건 변수 객체이다.
attr : 조건 변수 객체의 특징을 지정한다. NULL이면 기본 특징을 사용한다.

조건 변수 제거하기
int pthread_cond_destroy(pthread_cond_t *cond);

cond : 조건 변수 객체이다.

스레드 기다리기
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,
                              pthread_mutex_t *mutex, const struct timespec *abstime);

cond : 조건 변수 객체
mutex : 뮤텍스 객체. 조건 변수와는 별 상관 없을 것 같은 뮤텍스 객체를 사용한 이유
abstime : pthread_cond_timedwait 함수에서 사용한다. 지정한 시간 만큼 기다린다.

스레드 깨우기
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

cond : 조건 변수 객체

5. 멀티 스레드 프로그램의 장점과 단점

-장점-

응답성 향상 : 스레드는 새로운 프로세스를 만들지 않고, 코드조각을 만들기에
                  프로세스 복사보다 훨씬 더 빠르고 효율적으로 작동한다.

자원의 효율적인 사용 : 멀티 스레딩 프로그램은 각각의 cpu가 스레드를 담당해서 효율성이 늘어난다.

손쉬운 자원의 공유 : 멀티 스레드는 단일 프로세스에서 실행되기에 자원 공유가 쉽다.

-단점-

안정성 : 멀티 프로세스는 독립되어 있어서 하나가 문제가 생겨도 영향을 받지 않지만
           멀티 스레드는 하나의 프로세스에서 실행되기 때문에
           특정 스레드에서 발생한 문제가 다른 스레드에까지 영향을 끼칠 수 있다.

난해한 프로그래밍 기술  : 코드의 흐름을 이해하고 결과를 예측하기가 까다롭다.

소스 코드 받기

2016년 5월 23일 월요일

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

1. 입출력 다중화란?

입출력 다중화 기술은 여러개의 입력과 출력을 관리하는 기술이다.
입출력 대상은 파일이므로 여러 파일의 입출력을 다룰 수 있다

1) 입출력을 관리하고자 하는 파일 지정번호의 그룹을 만든다.
2)관리 파일 그룹에 있는 파일 중 읽을 데이터가 있는 파일이 있는지를 확인해서 반환한다.
   운영체제는 관리 파일 그룹에 있는 파일에 데이터 변화가 발생하면 이를 체크 한다
3)만약 읽을 데이터가 있는 파일이 있다면 읽기 함수로 데이터를 읽어서 처리한다
4)처리 후에는 2번으로 이동해서 2~4를 반복 수행한다.

2. select 함수로 입출력 다중화 구현

select 함수는 여러 파일 중 어떤 파일에 데이터 변화가 생겼는지를 알려줘서,
작업할 파일을 선택할 수 있도록 해준다.

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, struct timeval *timeout);

nfds : select 함수는 파일의 그룹을 관리한다. 그룹이 몇 개의 파일을 포함하는지 알려준다.
readfds : 읽을 데이터가 있는지 검사
writefds : 쓸 데이터가 있는지 검사
exceptfds : 비트 테이블의 크기 최대 1024비트의 크기를 가진다.
timeout : 입출력 변화가 있을 때까지 기다리는데 제한 시간을 지정할 수 있다.
             NULL 이면 데이터 변화가 있을 때 까지 무한정 기다린다.

fd_set 관리를 위한 매크로 함수

select 함수의 핵심은 fd_set 관리에 있다. 1024비트 크기의 비트 필드로 파일 플래그 정보를 저장한다. 몇 가지 매크로 함수가 제공된다.

1) FD_ZERO(fd_set *fds) : fd_set를 초기화 한다. 모든 프래그 값을 0으로 만든다.
2) FD_SET(int fdnum, *fds) : 파일 지정번호 fdnum을 관리 위해서 fd_set에 추가한다.
                           select 함수는 이 파일에 데이터 변화가 생기면 플래그를 1로 한다.
3) FD_ISSET(fdnum, *fds) : fds에 포함된 fdnum 파일에 데이터 변화가 있는지 확인한다.
4) FD_CLR(fdnum, *fds) : fds 에서 fdnum을 제외한다.
       클라이언트가 연결 종료를 해서 더 이상 관리할 필요가 없는 경우에 주로 사용한다.

3. 소켓 프로그래밍과 입출력 다중화의 결합

프로그램 시나리오

1) socket 함수 호출
2) bind 함수 호출
3) listen 함수 호출
4) socket 함수로 만든 듣기 소켓을 fd_set에 추가한다.
5) select 함수를 호출한다.
6) 듣기 소켓 혹은 연결 소켓에 읽을 데이터가 들어오면 select 함수는 반환한다
7) FD_SET 매크로 함수로 어느 소켓에 읽을 데이터가 있는지 확인한다.
  - 듣기 소켓에 읽을 데이터가 있다면, accept 함수를 호출해서 클라이언트와 연결한다.
     이때 만들어진 연결 소켓은 fd_set에 추가한다.
  - 연결 소켓에 읽을 데이터가 있다면 read 함수로 데이터를 읽어서 처리한다.
8) 5번으로 되돌아간다.

4. 입출력 다중화의 특징과 적용처

입출력 다중화는 프로세스나 스레드를 만들지 않고도 여러 소켓을 처리할 수 있다.
IPC를 사용할 필요도 없고 동기화를 위해 고민할 필요도 없다.

그러나 동시 실행이 아니기 때문에 이에 따르는 제약도 있다.
데이터를 읽어서 처리하고 응답하는데 많은 시간이 걸리는 서비스에는 적당하지 않다.
그 시간 동안 다른 입출력은 대기하기 때문이다.
입출력 다중화를 활용하기에 가장 좋은 서비스는 채팅 서비스 같은 메시지 전달 서비스다.

소스 코드 받기

2016년 5월 22일 일요일

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

1. 소켓 입출력 함수의 데이터 입출력 제어

TCP 전용 입출력 함수
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd : 소켓 지시사
buf : 데이터 저장을 위한 버퍼
len : 버퍼의 크기

UDP 전용 입출력 함수
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flage,
                                        struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, void *buf, size_t len, int flage,
                                      const  struct sockaddr *dest_addr, socklen_t *addrlen);

sockfd : 소켓 지시사
buf : 데이터 저장을 위한 버퍼
len : 버퍼의 크기
src_addr : 데이터를 보낸 측의 주소
dest_addr : 데이터를 받을 측의 주소

소켓 함수는 flags 매개변수를 이용하면 여러 일을 할 수 있다

MSG_PEEK : 데이터를 읽은 후 버퍼의 데이터를 지우지 않는다. 이 값은 여러 서브 루틴이 하나의 소켓으로부터 값을 읽기 위해서 대기하고 있을 때 유용하다

MSG_DONTROUTE : 데이터 전송시 라우팅 테이블을 참조 하지 않을 것을 명시한다.
결국 지역 네트워크에서만 패킷을 전송하겠다는 의미다.

MSG_DONTWAIT : 비봉쇄 통신을 시도한다.

MSG_OOB : out-of-band 데이터로 전송함을 나타낸다.

2. OOB 통신

OOB는 긴급히 전달해야 할 데이터가 있을 때 사용한다.
정보의 우선순위가 아닌 중요한 데이터라는 것을 알려주는 것이다.

버퍼가 비어지길 기다리지 않고 바로 전송한다.
Nagle  알고리즘을 무시한다.
TCP 소켓에서만 사용된다

OOB 통신 방법

보내는 측 : MSG_OOB만 설정하면 된다.
send(sockfd, buf, MAX_LINE-1, MSG_OOB);

받는 측 : 시그널을 발생시켜야 하므로 약간 더 복잡하다.
signal(SIGURG, urg_handler);
fcntl(sockfd, F_SETOWN, getpid());

send(sockfd, "A", 1, MSG_OOB);

3. 표준 입출력 함수

리눅스는 소켓도 파일로 다루기 때문에 표준 입출력 함수를 사용할 수 있다.

스트림을 제어하는 표준 입출력 함수

저수준 함수를 사용하면 데이터를 저장하기 위한 버퍼만 제공할 뿐, 
전혀 관리를 하지 않는다. 개발자가 직접 데이터를 관리해야 한다.
개발자가 구분자를 지정해서 활용해야 한다.
이럴 때 표준 입출력 함수를 이용하면 간단하게 처리할 수 있다.

소켓 프로그래밍이라면 read함수 대신 fgets를, write 함수 대신 fputs를 사용하면 된다.

파일 지정 번호로부터 파일 스트림 가져오기

소켓은 int 형의 파일 지정 번호를 사용한다.
하지만 모든 표준 입출력 함수는 파일 스트림 객체임 FILE 자료형을 사용하며, 
형 변환이 불가능하다. 
fdopen 함수를 이용하면 파일 지정 번호를 파일 스트림으로 열수 있다.

#include <stdio.h>
FILE *fdopen(int fd, const char *mode);

fd : 파일 스트림을 얻어올 소켓 지시자
mode : 파일 스트림의 접근 방식을 지정한다.
 r : 읽기 전용으로 연다
 w : 쓰기 전용으로 연다
 r+ : 읽기/쓰기로 연다
 w+ : 읽기/쓰기로 연다. 만약 파일이 존재하지 않으면, 파일을 새로 만든다. 
        기존에 파일이 존재 한다면, 파일을 크기 0으로 만든다.

사용이 끝난 후에는 fclose 함수로 닫아줘야 한다
#include <stdio.h>
int fclose(FILE *fp);

표준 입출력 함수의 한계

표준 입출력 함수들은 개발자의 수고를 덜어주기 위해 만들어졌다.
이러한 함수들은 대개 범용적으로 만들어져서 어떤 용도로든 무난하게 사용 가능하다
일반적으로 쓰기에는 편하지만 특수한 용도에는 적합하지 않을 수도 있으며 
성능에 문제가 있을 수도 있다.
표준 입출력 함수는 단지 읽기 쓰기 용도로 쓸 경우 
저수준 입출력 함수보다 20~30% 느리다.
대량의 데이터를 다루고 성능이 중요하다면 
표준 입출력 함수를 쓸 것인지 저수준 입출력함수를 쓸 것인지 고민해봐야 한다.

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

너무 내용이 길어서 3개씩 끊었다.

4. 공유 메모리

프로세스 간에 정보 교환 외에 정보 자체를 공유하는 경우가 있을 것이다.

공동으로 사용할 공간을 마련하고 이 공간에 정보를 넣어 공유하는 방식이다.

공유 메모리 생성

#include <sys/types.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmfla);

key : 공유 메모리는 커널에서 관리한다. 각 프로세스는 이를 이용해 접근할 공유 메모리를 식별한다.

size : 커널에 요청할 공유 메모리 공간의 크기다.
shmflg : 공유 메모리 접근 방식과 권한을 결정하기 위해 사용한다.
   - IPC_CREATE : 공유 메모리를 새로 생성한다.
   - IPC_EXCL : IPC_CREAT와 함께 사용한다. key 값을 가지는 공유 메모리 영역이 이미 전조하면 에러를 확인 할 수 있다.

공유 메모리 첨부와 분리하기


공유 메모리를 사용하기 위해서는 커널의 공유 메모리 공간을 프로세스에 첨부해줘야 한다.
1) 공유 메모리 공간을 생성한다
2) 프로세스 A가 공유 메모리 공간을 첨부한다.
3) 프로세스 B가 공유 메모리 공간을 첨부한다.
4) 이제 A와 B는 첨부된 공유 메모리 공간으로 데이터를 공유할 수 있다.

int shmdt(const void *shmaddr);


shmaddr : 분리할 공유 메모리를 가리키는 포인터로 shmat에서 사용했던 shmaddr을 그대로 사용한다.


공유 메모리 관리


shmctl 함수로 공유 메모리를 관리할 수 있다.
관리 요소는 공유 메모리 정보 가져오기, 권한설정, 공유 메모리 삭제 등이 있다.

int shmctl(int shmid, int cmd, struct shmid_ds *buf);


shmid : 공유 메모리 식별자

cmd : 공유 메모리 제어 값이다. 다음과 같은 값들이 있다.
    -IPC_STAT : 공유 메모리 정보를 가져온다.
    -IPC_SET : 공유 메모리 공간의 권한 변경을 위해 사용
    -IPC_SMID : 공유 메모리 공간을 삭제한다.
buf : 공유 메모리 정보를 저장한다.

공유 메모리의 장점

-빠른 수행 속도 : 공유 메모리는 데이터 저장을 위한 공간으로 메모리를 사용한다.
-효율성 : 여러 프로세스 간의 데이터 공유에 효율적이다.
-접근성 : key 이름만 알고 있으면 어떤 프로세스든 접근할 수 있다.

5. 세마포어

메모리를 공유하다 보면 읽고 쓰는데 동기화 문제가 일어날 수 있다.
동시에 여러 프로세스가 접근하지 못하도록 임계 영역을 만들어 간섭을 막을 수 있다.
세마포어는 커널에서 관리하는 자원으로 프로세스가 임계 영역을 진입하게 되고,
그렇지 못한 프로세스는 키가 반환되어 사용할 수 있을 때까지 기다려야 한다.

세마포어의 생성과 관리

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

key : 커널 관리 자원과 마찬가지로 key 값으로 생성하고 접근 할 수 있다.

nsems: 세마포어는 배열로 만들어지는데, 세마포어 집합의 크기를 결정하기 위해 사용한다.
semflg : 세마포어의 생성 방식을 결정한다.
    -IPC_CREAT : 커널에 key 값의 세마포어가 없다면 새로 생성한다.
    -IPC_EXCL : IPC_CREAT와 함께 사용한다. key 값의 세마포어가 이미 존재한다면 에러 값을 반환한다.

세마포어의 임계영역은 semop 함수로 관리한다.

int semop(int semid, struct sembuf *sops, unsigned nsops);

semid : semget 함수로 생성한 세마포어 식별자이다.

sops : 세마포어 제어를 위해 사용하는 세마포어 구조체이다.
nosps : 세마포에 집합의 개수이다. 하나의 프로세스만 진입하려면 1을 사용한다.

6. 시그널

시그널은 프로세스에게 비동기적인 사건을 알려주거나 혹은 사건을 동기화 시키는데 사용한다.

다양한 시그널

SIGHUP : 터미널에서 잃어버렸을 때 발생한다. 콘솔창을 닫을 때 발생한다.
SIGABRT : 프로그램의 비정상 종료시 발생한다
SIGINT : 컨트롤 + c를 입력할 때 발생한다. SIGINT 시그널이 프로세스로 전달되면 프로세스가 종료된다.
SIGIO : 비동기적인 입출력이 발생했을 때 사용한다.
SIGKILL : 프로세스를 강제로 죽이기 위해 사용한다
SIGEGV : 운영체제가 프로세스를 강제로 종료시킬 때 사용한다.
SIGSTOP : 프로세스를 일시 중단 할 때 사용한다.
SIGCONT : 멈춘 프로세스를 다시 움직일 때 사용한다.

시그널의 기본 행동 

종료 : 프로세스를 종료한다.
무시 : 기본적으로 무시되는 시그널은 없다고 봐도 된다. 하지만 개발자는 특정 시그널을 무시하도록 할 수 있다.
함수 실행 : 특정 시그널을 받았을 때 이를 처리할 함수를 호출할 수 있다.

시그널 함수를 이용해서 시그널 제어하기

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);

signum : 제어하기 원하는 시그널의 번호다.
handler :  signum 번호를 가지는 시그널이 발생 했을 때, 실행한 시그널 핸들러다.
   -SIG_IGN : 시그널을 무시한다. SIGKILL 과 SIGSTOP는 무시할 수 없다.
   -SIG_DFL : 시그널의 기본 행동을 한다.

역시 위의 내용은 각각 소스코드가 있다.
나의 깃허브에서 다운 받아서 해보길...;;;

소스 코드 받기

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

1. IPC에 대하여

컴퓨터 내부의 프로세스들이 서로 데이터를 주고 받을 수 있는 것
IPC는 그 자체가 도구가 아닌 '내부 통신을 위한 여러 도구'의 총칭이다
IPC 설비에서 제공하는 도구는 다음과 같다

파이프, 메시지 큐, 네임드 파이프, 공유 메모리,
메모리 맵, 시그널, 세마포어, 유닉스 도메인 소켓

2. 파이프 - 익명 파이프와 네임드 파이프

IPC 설비가 제공하는 내부통신 도구 중 가장 널리 사용되는 도구는 파이프다.
파이프는 프로세스 간의 파이프 모양의 단반향 통신 선로를 개설한다.
읽기 쓰기를 모두 수행하려면 두 개의 파이프를 만들어야 한다.

단방향 통신 : 한 반향으로만 통신 가능. 파이프
반이중 통신 : 양쪽 방향으로 통신이 가능 하지만, 동신 퉁신은 불가능. 무전기
전이중 통신 : 동시에 양쪽 방향을 통신 가능. 전화기, 소켓

파이프 만들기 
#include <unistd.h>
int pipe(int filedes[2]);

fileds : pipe 함수를 실행하면 매개변수로 파이프를 가리키는 파일 지정 번호를 돌려준다.

filedes[0] 은 읽기 전용, filedes[1] 는 쓰기 전용 파이프다.
이 두 파이프로 전이중 통신을 구현한다.

파이프는 부모 프로세스 <-> 자식 프로세스 통신을 위해 사용된다.

파이프 예제

//pip 함수로 파이프를 만든다.
        if (pipe(fd) < 0)
{
perror("pipe error : ");
return 1;
}
//fork 함수로 자식 프로세스를 생성한다.
if ((pid = fork()) < 0)
{
return 1;
}
// 만약 자식프로세스라면 파이프에 자신의 PID 정보를 쓴다.
else if (pid == 0)
{
close(fd[0]); //읽기 전용 파이프 닫기 
while(1)
{
i++;
write(fd[1], (void *)&i, sizeof(i)); //쓰기 전용 파이프
sleep(1);
}
}

// 만약 부모프로세스라면 파이프에서 데이터를 읽어 들인다.
else
{
close(fd[1]); //쓰기 전용 파이프 닫기
while(1)
{
read(fd[0], (void *)&buf, sizeof(buf)); //읽기 전용 파이프
printf("> %d\n", buf);
}
}

익명 파이프와 네임드 파이프

내부 통신을 위해서 사용할 수 있는 가장 간단한 도구는 파일이다.
파이프와 파일의 가장 큰 차이는 '이름'을 가지고 있는지 여부다.
pipe 함수로 만든 파이프는 이름을 가지고 있지 않기에
외부의 프로세스는 이 파이프를 사용할 수 없다.
파이프는 부모/자식 프로세스 간에만 사용할 수 있다.
외부 프로세스와 파이프 통신을 하기 위해서는 파이프에 이름을 지어주면 된다.

네임드 파이프의 생성

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

pathname : 네임드 파이프의 이름이다. 네임드 파이프는 파일 형태다. 파일이므로 이름만으로도 어디에서나 파이프에 접근할 수 있다.
mode : 파일이므로 권한을 설정해야 한다.

네임드 파이프의 예제

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>

#include <unistd.h>
#include <stdio.h>
#include <string.h>

#define MAXLINE 1024
int main(int argc, char **argv)
{
int rfd, wfd;
char buf[MAXLINE];
//mkfifo 함수로 네임드 파이프를 만든다.
mkfifo("/tmp/myfifo_r", S_IRUSR|S_IWUSR);  //읽기용 네임드 파이프
mkfifo("/tmp/myfifo_w", S_IRUSR|S_IWUSR);  //쓰기용 네임드 파이프
//네임드 파이프를 연다
        if( (rfd = open("/tmp/myfifo_r", O_RDWR)) == -1)
{
perror("rfd error");
return 0;
}
if ( (wfd = open("/tmp/myfifo_w", O_RDWR)) == -1)
{
perror("wfd error");
return 0;
}
//읽기 네임드 파이프에서 데이터를 읽어서 쓰기 네임드 파이프에 쓴다.
while(1)
{
memset(buf, 0x00, MAXLINE);
if(read(rfd,buf,MAXLINE) < 0)
{
perror("Read Error");
return 1;
}
printf("Read : %s", buf);
write(wfd, buf, MAXLINE);
//쓰기 버퍼를 비워서 즉시 파일에 쓰도록 한다. 파일 내에서 위치를 재정의 하는 함수다.
lseek(wfd, 0, SEEK_SET);
}
}

3. 유닉스 도메인 소켓

IPC와 네트워크 통신은 통신 영역만 다를 뿐 본질적으로 차이가 없다.
소켓으로도 내부 프로세스 간 통신을 할 수 있다.

소켓 함수에서 첫번째 매개 변수를 바꾸면 가능하다
int socket(int domain, int type, int protocol);

struct sockaddr_un sockaddr;
socket (AF_UNIX, SOCK_STREAM, 0);
strncpy(serveraddr.sun_path, "filename", 8);

유닉스 도메인 소켓의 용도

유닉스 도메인 소켓 방식은 소켓 함수 뿐만 아니라 소켓 프로그래밍 모델까지 그대로 사용할 수 있다. 간단히 서버/클라이언트 모델을 구현할 수 있다.
대신, 파이프에 비해서 성능이 약간 떨어진다.
데이터를 가공하기 위한 여러 프로세스로 구성된 미들웨어와 같은 큰 규모의 프로그램에서 내부 데이터 통신을 원한다면 유닉스 도메인 소켓을 사용하길 권장한다.

소스 코드 보기