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);

유닉스 도메인 소켓의 용도

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

소스 코드 보기

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

멀티 프로세스 소켓 프로그래밍

1. 프로세스란?

프로그램을 실행할 때 만들어지는 것이 프로세스라고 한다.
프로그램을 실행하면 운영체제는 하드디스크에 저장된 프로그램을
그대로 실행하지 않고 프로그램을 메모리에 복사하고
복사한 프로그램의 이미지를 실행한다.

멀티 프로세스 운영체제는 동시에 여러 프로세스를 관리해야 하기에
각 프로세스에 유일한 일련번호(PID)를 부여한다.

2. 멀티 프로세스 

멀티 프로세싱이 동시에 여러 프로세스를 운용한다는 의미를 가지고 있지만
실제로는 같은 시간에 여러 프로세스가 동시에 실행되지는 않는다.
아주 짧은 시간 동안 프로세스를 번갈아 가면서 실행해서 동시성을 제공한다.

3. 멀티 프로세스 프로그래밍

fork 함수로 프로세스 복사하기
리눅스는 fork 함수로 프로세스를 복사할 수 있다.
fork 함수는 새로운 프로세스를 만드는 함수가 아니라 복사하는 함수이기에 실행까지는 되지 않는다.

fork 함수
#include <unistd.h>
pid_t fork(void);

fork 함수는 매개변수가 없다. fork함수를 호출하면 다음과 같은 값을 반환한다.
pid_t < 0 : fork 함수 실행 실패
pid_t == 0 : 자식 프로세스 생성 성공(자식 프로세스에는 0을 반환한다)
pid_t > 0 : 자식 프로세스 성공이이면 자식 프로세스의 PID 값이다.

fork 함수 들여다 보기
fork 함수를 호출하면 자식 프로세스를 만든다.
이때 명령과 데이터를 부모 프로세스로부터 상속 받는다.

지역 변수 값 및 전역 변수 값 : 복사된다.
열린 파일: 소켓을 포함한 모든 열린 파일의 파일 지정번호가 복사된다
시그널 : 상태를 알려주기 위해 사용한다.

execl 함수로 프로그램 실행하기

fork 함수를 자신을 복사한 프로세스를 만든다.
하지만 다른 프로그램을 실행 시킬 수는 없다.
외부 프로그램을 실행 시킬 수 있는 함수가 execl 함수다.

#include <unistd.h>
int execl(const char *path, const char *arg, ...);

-path : 경로를 포함한 실행 프로그램의 이름
-arg : 프로그램의 실행 인자

fork 함수와 execl 함수로 새로운 프로세스 실행하기
fork 함수로 자식 프로세스를 만들고,
자식 프로세스에서 execl 함수를 실행하면 새로운 프로세스를 실행할 수 있다.

프로그램 작동 시나리오는 다음과 같다.
1. 사용자 명령을 읽기 위한 프롬프트를 출력한다.
2. 키보드로 명령을 받는다.
3. fork 함수로 자식 프로세스를 만든다. 부모 프로세스는 자식 프로세스가 종료되는걸 기다린다.
4. execl 함수로 외부 프로그램(입력받은 명령)을 실행한다.
5. 외부 프로그램이 종료된다. 즉, 자식 프로세스가 종료된다.
6. 부모 프로세스는 자식 프로세스가 종료된 걸 확인하고, 다시 프롬프트를 띄운다.

자식 프로세스 기다리기
자식 프로세스의 생명이 부모 프로세스의 생명보다 대부분 짧다.
그러므로 부모 프로세스는 자식 프로세스의 종료를 기다리는 일이 중요하다.
프로세스는 종료 상태를 가진다.
프로세스가 종료가 되어야 자신의 모든 자원을 운영체제에게 되돌려 준다.

자식 프로세스의 종료 값은 wait 함수와 waitpid 함수로 얻을 수 있다.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

부모 프로세스는 자식 프로세스가 종료되면 그 값을 얻어온다.
waitpid 함수는 기다리기 원하는 프로세스의 pid를 지정할 수 있다.

4. 프로세스 관계

PID는 프로세스를 구분할 수 있는 유일한 번호로 시스템에서 유일하다.
프로세스는 독립된 프로세스이면서 동시에 집단에 포함된다.
프로세스 간의 관계가 형성된다.
부모와 자식 프로세스 간의 관계를 그룹으로 묶인다.
그룹은 다시 세션으로 묶인다.
만들어진 프로세스는 자신의 PID로 전체 시스템에서
유일한 프로세스임을 확인하는 동시에
부모/자식간의 관계와 그룹과의 관계를 맺게 된다.

5. 멀티 프로세스 네트워크 프로그램 개발

1) socket() -> bind() -> listen()까지는 기존 서버와 동일하다.
2) accept 함수를 호출한다.
3) accept 함수가 반환하면
4) fork 함수를 호출한다.
5) 부모 프로세스는 2번으로 간다.
6) 자식 프로세스는 7번으로 간다.
7) 클라이언트로부터 데이터를 읽는다.
8) 클라이언트에 데이터를 쓴다.

소스코드 

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#define MAXLINE 1024
#define PORTNUM 3600

int main(int argc, char **argv)
{
int listen_fd, client_fd;
pid_t pid;
socklen_t addrlen;
int readn;
char buf[MAXLINE];
struct sockaddr_in client_addr, server_addr;

if( (listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
return 1;
}
memset((void *)&server_addr, 0x00, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORTNUM);

if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) ==-1)
{
perror("bind error");
return 1;
}
if(listen(listen_fd, 5) == -1)
{
perror("listen error");
return 1;
}
//자식 프로새스의 시그널을 무시한다.
signal(SIGCHLD, SIG_IGN);
//부모 프로세스는 루프의 처음으로 되돌아가서 
//새로운 클라이언트가 연결되어 있는지 확인한다.
while(1)
{
addrlen = sizeof(client_addr);
//accept 함수로 클라이언트 연결을 가져온다.
client_fd = accept(listen_fd,
(struct sockaddr *)&client_addr, &addrlen);
if(client_fd == -1)
{
printf("accept error\n");
break;
}
//fork함수로 자식 프로세스를 만든다.
pid = fork();
//자식 프로세스가 실행할 코드다.  
//accept 함수로 만든 연결 소켓은 자식 프로세스에 그대로 상속된다. 
//자식 프로세스는 상속 받은 연결 소켓으로 클라이언트와 통신 하게 된다.
if(pid == 0)
{
memset(buf, 0x00, MAXLINE);
while((readn = read(client_fd, buf, MAXLINE)) > 0)
{
printf("Read Data %s(%d) : %s",
inet_ntoa(client_addr.sin_addr),
client_addr.sin_port,
buf);
write(client_fd, buf, strlen(buf));
memset(buf, 0x00, MAXLINE);
}
close(client_fd);
return 0;
}
//부모 프로세스가 실행하는 코드다.
//accept 함수로 생성된 연결 소켓은 자식 프로세스만 필요할 뿐, 
//부모 프로세스가 필요 없다. 그러므로 연결 소켓을 닫아준다.
                else if(pid>0)
                        close(client_fd);
}
return 0;
}

6. 멀티 프로세스 서버의 장점과 단점

장점 
1) 단순함 : 프로그램의 흐름에서 적절한 위치에 fork함수를 호출해주면 끝이다.
2) 안전성 : 부모 프로세스와 자식 프로세스는 완전히 독립된다. 어떤 프로세스가 잘못된 동작을 하더라도 다른 프로세스로의 영향을 최소화 할 수 있다.

단점 
1) 성능 : fork 함수를 호출하면 부모 프로세스를 그대로 복사한 프로세스가 만들어진다. 
이는 성능 저하를 일으킨다.
2) 프로세스간의 통신 문제 : 부모 프로세스와 자식 프로세스가 모두 독립되어 있다 보니 데이터 교환이 쉽지 않다. 

역시 이 부분도 깃 허브에 소스코드를 올렸다.


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

UDP - 품질보다 연속성을 중시하는 서비스

UDP 특징 
- 신뢰할 수 없음 : UDP 는 메세지를 보내고 잊어 버린다.
- 정돈 되지 않음 : UDP는 일련번호가 없다. 2개 이상의 패킷이 전달되면 정돈 할 수 없다.
- 가벼움 : 신뢰성을 포기한 대신 가볍게 움직일 수 있다.

소켓 생성
socket(AF_INET, SOCK_DGRAM, 0);

데이터 쓰기
#include <sys/type.h>
#include <sys/socket.h>
int sendto(int sockfd, const void *msg, size_t len, int flags,
           const struct sockaddr *to, socklen_t tolen);

-sockfd : 소켓 함수로 생성한 소켓 지정번호
-msg : 전송할 데이터
-len : 전송할 데이터의 크기
-flags : 데이터 전송방식, 일반적으로 0을 사용한다.
-to : 보낼 인터넷 주소와 포트 번호를 포함하는 sockaddr 구조체
-tolen : 인터넷 주소 구조체 sockaddr의 크기

데이터 읽기
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int sockfd, void *buf, size_t len, int flags,
              const struct sockaddr *from, socklen_t fromlen);

-sockfd : 소켓 지정 번호
-buf : 읽은 데이터를 저장할 변수
-len : 읽을 데이터의 크기
-flags : 데이터 전송 방식, 일반적으로 0을 사용한다.
-from : 데이터를 보낸 인터넷 주소와 포트 번호를 포함
-fromlen :  sockaddr 구조체의 크기


소스코드는 내 깃허브에서 다운 받을 수 있다.
깃허브 방문하기

2016년 5월 14일 토요일

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

-TCP의 특징-

연결 지향 - 두 컴퓨터를 연결하는 전용의 연결회선을 만든다.(세션을 생성한다)

신뢰성 있는 데이터 교환 - 전송한 데이터를 제대로 받았는지 확인 한다.
                                   만약 실패하면 재전송 한다.

전 이중 통신 - 읽고 쓰기를 모두 할 수 있다.

-TCP의 과정-

1. 클라이언트가 패킷을 전송한다.
2. 패킷을 받은 서버는 일련 번호에 1을 더한 값을 ACK 값을 설정해서 전송한다.
3. 클라이언트는 서버의 ACK 값을 읽어서 패킷이 제대로 전달된 것을 알게 된다
4. 1~3을 반복하면서 데이터를 주고 받는다.

-소스코드-
아래 코드는 "뇌를 자극하는 TCP/IP 소켓 프로그래밍" 코드다.
꼭 책을 사서 공부하자~ 이 블로그는 공부한 내용을 기억하기 위해 요약 한 것이다.

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

#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>

#define PORT 3600

//주고 받을 데이터를 구조화 하기 위해 cal_data 구조체를 정의 한다.
struct cal_data
{
        int left_num;
        int right_num;
        char op;
        int result;
        short int error;
};

int main(int argc, char **argv)
{
        struct sockaddr_in client_addr, sock_addr;
        int listen_sockfd, client_sockfd;
        int addr_len;
        struct cal_data rdata;
        int left_num, right_num;
        short int cal_result;

//IP와 연결지향 TCP 기반 통신을 위한 듣기 소켓을 생성한다.
        if( (listen_sockfd  = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        {
                perror("Error ");
                return 1;
        }

//듣기 소켓을 위한 주소 영역, 기다릴 주소와 포트 번호를 생성한다.
        memset((void *)&sock_addr, 0x00, sizeof(sock_addr));
        sock_addr.sin_family = AF_INET;
        sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        sock_addr.sin_port = htons(PORT);

//bind 함수로 소켓 특성을 묶어준다.
        if( bind(listen_sockfd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1)
        {
                perror("Error ");
                return 1;
        }

//수신 대기열을 생성한다.
        if(listen(listen_sockfd, 5) == -1)
        {
                perror("Error ");
                return 1;
        }

        for(;;)
        {
                addr_len = sizeof(client_addr);

//accept 함수로 수신 대기열에 클라이언트 연결이 있는지 확인한다.
                client_sockfd = accept(listen_sockfd,
                        (struct sockaddr *)&client_addr, &addr_len);
                if(client_sockfd == -1)
                {
                        perror("Error ");
                        return 1;
                }
//read 함수로 소켓에서 데이터를 읽어온다.
                read(client_sockfd, (void *)&rdata, sizeof(rdata));

                rdata.error = 0;

//읽어온 데이터는 네이퉈크 바이트 순서를 따른다. 
//ntohs 함수로 호스트 바이트 순서로 변환한다.
                left_num = ntohs(rdata.left_num);
                right_num = ntohs(rdata.right_num);
//op 값을 확인해서 필요한 사칙연산 루틴을 실행한다.
//나누기(/)에서 분모가 되는 오른쪽 피연산자가 0이면 error 값을 설정한다.
                switch(rdata.op)
                {
                        case '+':
                                cal_result = left_num + right_num;
                                break;
                        case '-':
                                cal_result = left_num  - right_num;
                                break;
                        case '*':
                                cal_result = left_num * right_num;
                                break;
                        case '/':
                                if(right_num == 0)
                                {
                                        rdata.error = 2;
                                        break;
                                }
                                cal_result = left_num / right_num;
                                break;
                        default:
                                rdata.error = 1;

                }
//계산 값과 에러 값을 네트워크 바이트 순서에 맞게 변환한다.
                rdata.result = htonl(cal_result);
                rdata.error = htons(cal_result);
//write 함수로 계산 결과를 클라이언트에 전송한다.
                write(client_sockfd, (void *)&rdata, sizeof(rdata));
                close(client_sockfd);
        }

        close(listen_sockfd);
        return 0;
}

2016년 5월 12일 목요일

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

-인터넷 주소 클래스-

A클래스 - 국가 수준의 네트워크 구성 (0.0.0.0 ~ 127.255.255.255)
B클래스 - 종합 대학교나 규모가 큰 연구소 이상 (128.0.0.0 ~ 191.255.255.255)
C클래스 - 중소규모의 기업 (192.0.0.0 ~ 223.255.255.255)
D클래스 - 멀티캐스트 네트워크 구성(224.0.0.0 ~ 239.255.255.255)
E클래스 - 남겨둔 영역(240.0.0.0 ~ 255.255.255.255)

-리눅스에서 인터넷 주소 변환-

점 표기 주소 방식 -> 바이너리 인터넷 주소로 변환 함수

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);

cp : 변환하려는 인터넷 주소
inet_addr : 변환된 IP인터넷 주소를 반환

inp : 변환된 값을 넘겨줌
inet_aton : IPv6에서도 사용 가능한 함수

바이너리 인터넷 주소 -> 점 표기 주소 방식로 변환 함수

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);

-도메인 이름-

도메인 : 다른 문자를 이용해 이름을 붙인 주소

인터넷 주소 목록 가져는 함수
#include <netdb.h>

struct hostnet *gethostbyname(const char *name);

name: 변환할 도메인 이름

-윈도우에서 인터넷 주소, 도메인 이름 변환-

점 표기 주소 방식 -> 바이너리 주소로 변환 하는 함수
unsigned long inet_addr (
     __in const char *
)

도메인 이름에서 주소 정보 얻어오는 함수
gethostbyname() 함수를 사용한다.


2016년 5월 5일 목요일

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

세상에는 수많은 컴퓨터가 있고, 수많이 연결되어 있다.
그러다 보니 데이터를 처리하는 방식이 다른 경우가 많다.
인터넷으로 돌아다닐 데이터는 상대방 컴퓨터가 어떤 CPU를 쓰는지 모른다.
그래서 이것을 통일 시켜줘야 한다.

서로 다른 데이터 읽는 방식의 교류를 위해 "네트워크 바이트 순서" 를 정했다.
인터넷으로 데이터를 보낼 때는 무조건 네트워크 바이트 순서로 변환해서 내보내야 한다.

바이트 순서 변환 함수는 다음과 같다

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostdata)
unsigned short int htons(unsigned short int hostdata)

unsigned long int ntohl(unsigned long int netdata)
unsigned short int ntohs(unsigned short int netdata)

자세히 보면 일정한 패턴이 있다.

데이터를 보낼 때
htonl = host to network (long)
htons = host to network (short)

데이터를 받을 때
ntohl = network to host (long)
ntohs = network to host (short)

예제)
//쓸 때 (보낼 때)
mydata.age = htons(25);
mydata.birthday = htonl(19811203);

//읽을 때 (받을 때)
read((void*)&mydatainfo, .....);
ntohs(mydatainfo.age);
ntohl(mydatainfo.birthday);

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

오늘은 어린이 날인데도 집에서 공부하고, 블로그나 올리는거다 ;;;
1. 리눅스에서는 모든게 다 파일이다.
2. 표준 입력, 표준 출력, 표준 에러들이 있다.
3. 소켓도 파일로 처리한다.

파일은 열고 -> 쓰고/읽고 -> 닫고 의 과정을 거친다.
모든 파일에는 소유자와 그룹별로 읽기, 쓰기, 실행 의 권한을 설정할 수 있다.

예를 들어 이런 권한이 있다고 하자
drwxr-xr-x
이게 뭔 x소리인가 하겠지만....;; 끊어 읽으면 편하다.
d rwx r-x rw-
d는 디렉토리를 뜻한다. 파일일 때는 -로 쓴다.
다음 3자리는 소유자 권한 부분이다.(rwx 니깐 읽고, 쓰고, 실행까지 가능한거다)
다음 3자리는 그룹 권한 부분이다. (r-x 니깐 읽고, 실행만 가능하다)
다음 3자리는 기타 권한 부분이다. (rw- 니깐 읽고, 쓰기만 가능하다).

위의 권한을 숫자로 표현하기도 한다.
rwx = 7 = 111(2진수),  r-x = 5 = 101(2진수), rw- = 6 = 110(2진수)
다 합해서 756 이라고 표현한다.

chmod 756 파일명  <--- 이 명령어로 해당 파일에 위와 같은 권한을 줄수 있다.

파일 열기

open 함수로 파일을 열수 있다.

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

pathname : 열고자 하는 파일 이름
flags : 파일의 접근 방식과 생성 방식을 결정
mode : 파일의 권한을 결정하기 위해 사용

파일 읽기/쓰기

read 함수로 파일의 내용을 읽을 수 있다.

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

fd : open 함수로 생성된 파일 지정 번호다
buf : 읽은 데이터를 저장하기 위한 변수다
count : 읽은 데이터의 크기다.

write 함수로 파일의 내용을 쓸 수 있다.

#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);

fd : open 함수로 생성된 파일 지정 번호다
buf : 쓰려는 데이터다.
count : 쓰려는 데이터의 크기다.

파일 닫기

close 함수로 파일을 닫을 수 있다.

close(int fd);

fd : open 함수로 생성된 파일 지정 번호다.

2016년 5월 4일 수요일

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

소켓 네트워크 프로그래밍의 흐름

1. 소켓을 만든다. -> socket 함수 사용
2. 클라이언트 : 연결을 시도한다 -> connect 함수 사용
   서버 : 연결을 기다린다. -> accept 함수 사용
3. 소켓으로 통신한다. -> read / write 함수 사용
4. 소켓을 닫는다. -> close 함수 사용

클라이언트와 서버의 프로그램의 흐름

클라이언트
socket() -> connect() -> read()/write() -> close()

connect(): 서버에 연결 시도한다.

서버
socket() -> bind() -> listen() -> accept() -> read()/write() -> close()

bind() : 소켓의 특성을 정의 한다. (프로토콜, IP주소, 포트)
listen() : 여러 클라이언트가 접속 요청할 때 대기열을 지정한다.
accept(): 클라이언트의 연결을 수락한다.

socket 함수
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain - AF_UNIX : 시스템 내부에서 프로세스 간의 통신
           - AF_INET : 인터넷 영역에서 컴퓨터 간의 통신(IPv4)
           - AF_INET6 : 인터넷 영역에서 컴퓨터 간의 통신(IPv6)

type - SOCK_STREAM : TCP 기반 통신
       - SOCK_DGRAM : UDP 기반 통신
protocol - IPPROTO_TCP : TCP 프로토콜
            - IPPROTO_DUP : UDP 프로토콜
반환값 - 성공하면 0 이상의 소켓 지정 번호, 실패하면 -1

bind 함수
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

sockfd : socket 함수로 생성된 소켓
my_addr :  IP주소/port 번호를 저장하기 위한 변수가 있는 구조체
addrlen : my_addr의 데이터 크기
반환값 : 성공하면 0, 실패하면 -1

listen 함수
int listen(int sockfd, int backlog)

sockfd : 소켓 지정번호
backlog : 수신 대기열의 크기
반환값: 성공하면 0, 실패하면 -1

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

sockfd : 소켓 지정 번호
addr : 클라이언트의 주소와 포트 정보를 구조체에 복사
addrlen : sockaddr 구조체의 크기

connet 함수
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

sockfd : 소켓 지정 번호
serv_addr : 연결할 서버의 IP 주소와 포트등의 정보를 알려주기 위해 사용
addrlen : server_addr의 크기
반환 값 : 연결 성공하면 0, 실패하면 -1

read/write 함수
ssize_t read(int fd, void *buf, size_t count);
ssize_t write (int fd, void *buf, size_t count);

fd : 열린 파일의 파일 지정 번호
buf : 읽기/쓰기 데이터가 저장될 버퍼 변수
count :  읽기/쓰기 데이터의 count 크기
반환값 : 성공하면 읽기/쓰기 데이터의 크기(byte 단위), 실패하면 -1

close 함수
int close(int sockfd);

sockfd : 닫고자 하는 소켓 지정 번호

-윈도우 소켓 프로그래밍-
윈도우는 윈속을 이용해서 소켓 프로그래밍을 한다.
나머지는 다 동일한데 몇 가지만 다르다.

1. 윈속을 사용하기 위해 ws2_32.lib 를 속성에 추가해야 한다
2. WSAStartup 함수를 호출한다.
3. recv 와 send를 통해 데이터 입출력을 해야 한다.
4. closesocket 로 소켓 해제
5. WSACleanup로 WSAStartup 함수 해제

WSAStartup 함수
int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData);

wVersionRequested : 윈속의 버전 (2, 2 를 쓰면 된다.)

recv / send 함수
int recv(SOCKET s, char *buf, int len, int flags);
int send(SOCKET s, char *buf, int len, int flags);

s : 소켓 지정 번호
buf : 읽기/쓰기 데이터가 저장될 버퍼 변수
len : buf의 크기
flags : 소켓 제어를 위한 flags(0을 쓰면 된다)

closesocket 함수
closesocket(SOCKET s);

s: 해체할 소켓의 지정 번호

WSACleanup함수
WSACleanup() : 윈속 함수 끝

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

3강에서는 소켓과 네트워크에 관한 간단한 요약을 소개한다.

소켓이란?

네트워크 프로그램을 작성하기 위한 라이브러리를 포함한 API를 제공한다.

-서버/클라이언트 모델-
서버의 IP와 포트 번호를 알고 있으면
클라이언트에서 연결해서 서로 통신을 주고 받을 수 있게 해준다.

-명시된 포트 번호 -

20, 21 - FTP
22 - SSH
23 - 텔넷
25 - SMTP
37 - TIME
53 - 도메인
80 - HTTP
110 - POP3

1~1023 까지는 명시적으로 할당된 포트 번호이기에
개발자가 네트워크 프로그래밍을 할 때는
이 범위의 포트 번호는 피해서 작성해야 한다.

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

2강은 네트워크 프로그래밍을 위해서 환경 설정하는 장이다.

윈도우와 리눅스를 같이 써야 하기에
vmware 에 우분투 14를 설치하고 번갈아가며 공부 했다.

리눅스 코딩 환경
os - 우분투 14
IDE - 코드블럭
컴파일러 - GCC, G++

윈도우 코딩 환경
os - 윈도우 10
IDE - vs 커뮤니티 2015

리눅스 같은 경우에는 바로 코딩해도 되지만

윈도우에서 소켓 프로그래밍을 하려면
프로젝트에서 오른 클릭 -> 속성 -> 구성속성 ->
링커 -> 입력 -> 추가종속성 -> 편집
에서 ws2_32.lib 를 입력해주고 확인 적용을 눌러줘야 한다.

아래 그림과 같이 해줘야 한다.


리눅스나 윈도우나 프로젝트는 콘솔에서 C언어로 작성한다.

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

열혈강의로 소켓 프로그래밍을 공부했었고 여기에 요약?을 했었다.
근데 뇌자극 시리즈가 더 나에게는 뇌에 잘 들어오더라...;;
그래서 각 장을 요약 정리 할려고 한다.
현재 11강까지 마친 상태다.

OSI 7 계층

응용계층 - 응용프로그램 HTTP, FTP 같은 프로토콜
표현계층 - 데이터를 인터넷으로 전송할 때 데이터를 다루기 좋은 형태로 가공
세션계층 - 컴퓨터끼리 연결 되었을 때 소프트웨어의 통신을 관리하기 위한 규격
전송계층 - 신뢰성 있는 데이터 전송을 보장하기 위한 규격
네트워크 계층 - 네트워크 경로를 찾아주기 위한 규격
데이터 링크 계층 - 오류 제어와 흐름 제어를 위한 규격
물리계층 - 시스템을 연결하기 위한 전기/물리적 규격

TCP/IP 4계층 - 위의 OSI 7계층을 4개로 묶은 계층이다.

응용 계층(응용 + 표현 계층) - HTTP, FTP, DNS, POP3, SNMP
전송 계층(세션 + 전송 계층) - TCP, UDP
인터넷 계층(네트워크 + 데이터 링크 계층) - ICMP, IP, ARP, RARP
물리 계층 (물리 계층) - 이더넷, 프레임 릴레이, 토큰 프레임, ATM