반응형

15-1. 표준 입출력 함수의 장점

표준 입출력 함수의 장점

이식성이 좋아진다.

 - 모든 시스템은 ANSI 표준 C를 지원한다.

효율성을 높일 수 있다.

 - 표준 입출력 함수에 의한 버퍼를 제공받는다. (표준 입출력버퍼, 소켓 버퍼)

 - 소켓 생성 버퍼 목적 :  프로토콜의 완성을 위한 것.

 - 표준 입출력 버퍼 목적 : 성능 향상


파일 포인터와 파일 디스크립터

파일 포인터

 - 표준 입출력 함수 호출을 위해서 필요한 파일 포인터. = FILE 구조체의 포인터.

파일 디스크립터를 이용한 시스템 입출력

 - 시스템 입출력 함수 사용 시 필요

 - 정수값이다.

※ 소켓 기반의 표준 입출력 함수 사용을 위해 파일 디스크립터를 기반으로 파일 포인터를 생성.


15-2. 표준 입출력 함수의 사용

파일 디스크립터 -> 파일 포인터

#include<stdio.h>

FILE * fdopen(int files, const char *mode);

리턴 : 성공시 FILE 포인터, 실패시 NULL


fdopen 후 파일 디스크립터와 파일 포인터 상태


※ 표준 입출력 함수를 사용할 경우 : 파일 버퍼 -> 소켓 버퍼 -> 실제 전송 혹은 수신.


파일 포인터 -> 파일 디스크립터

#include<stdio.h>

int fileno(FILE *stream);

리턴 : 파일 포인터가 가리키는 파일의 디스크립터 리턴.

stream : 파일 포인터. 파일 디스크립터는 파일 모드와는 상관이 없다.


15-3. 소켓 기반의 표준 입출력 함수의 사용

파일에 버퍼가 필요한 이유

CPU가 직접 I/O를 하면 비효율적이기 때문에 버퍼에 그 내용을 저장했다가 파일로 전송한다.

flag를 두어 CPU가 여유 있을 때 system call을 통해 버퍼 내용을 파일로 보낸다.

system call을 이용하면 모아서 입출력 하는 것이 힘들다.

하지만 파일 버퍼를 이용하면 데이터를 모아서 입출력하는 데 좋아진다.

반응형
반응형

유선 : 고객에게 서비스를 제공. 멀티캐스트 방식(가입이 필요)

안테나 방송 : 서비스를 지정된 채널로 제공. 브로드캐스트 방식(가입 필요 없음)


14-1. 멀티캐스트(Multicast)

멀티캐스트 전송 방식.

UDP를 기반으로 하는 전송 방식. 

멀티캐스트 그룹(클래스 D에 속하는 IP주소)을 기반으로 멀티캐스트 패킷을 주고 받음.

하나의 멀티캐스트 패킷은 라우터를 통해서 다수의 호스트에 전송.

(한 번만 방송하면 가입자 모두 시청 가능.)

(정보를 단방향으로 제공할 때 주로 사용.)


라우팅(Routing)과 TTL(Time To Live)

라우팅 : 라우터에 의해서 패킷이 경로를 찾는 과정

TTL 

거쳐갈 수 있는 라우터의 수(홉)를 의미. 

이 값이 0이 되면 패킷은 더 이상 전달되지 못하고 소멸된다.


멀티캐스트 Sender와 Receiver

Sender : 멀티캐스트 그룹에 데이터를 전송하는 호스트

Receiver : 멀티캐스트 그룹으로부터 데이터를 수신하는 호스트

※ Sender는 서버(제공자), Receiver는 클라이언트로 구분 지을 수 있지만 요청하는 부분과는 관련이 적다.


멀티캐스트 Sender와 Receiver

 Sender Receiver 
 UDP 소켓 생성
 반드시 TTL 설정(소켓 옵션 설정)
 멀티캐스트 그룹으로 데이터 전송
UDP 소켓 생성
멀티캐스트 그룹 지정(ip_mreq)
멀티캐스트 그룹 가입(소켓 옵션 설정) 

Sender TTL 설정

state = setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&multi_TTL, sizeof(multi_TTL));


Receiver 멀티캐스트 그룹 지정.

struct ip_mreq

{

    struct in_addr imr_multiaddr;    // 가입하고자 원하는 D 클래스 ip주소

    struct in_addr imr_interface;     // 나의 ip 주소. htonl(IN_ADDR);을 써주면 된다.

};

※ 서로 포트는 일치 해야 한다.


14-2. 브로드캐스트(Broadcast)

브로드캐스트 전송방식

UDP를 기반으로 하는 전송 방식(=멀티캐스트와 같다.)

일반적으로 UDP 패킷과의 차이점은 전송 목적지 ip주소 뿐이다.

(멀티캐스트와의 차이점)

동일 네트워크에 속하는 모든 호스트에 동시 전송

인터넷 상에서 지역 네트워크 내에서만 브로드캐스를 허용한다. (네트워크 부하를 고려)


주소선택에 따른 브로드캐스트 방식 구분

지정된 브로드캐스트

예 : 192.12.31.255 -> 자신이 속한 IP주소만 설정 가능. (지역적 브로드캐스트를 더 많이 사용.)


지역적 브로드캐스트 

예 : 255.255.255.255 -> 알아서 현재 네트워크(LAN)에 브로드캐스트.


반응형
반응형

13-1. recv & send 입출력 함수

read & write 함수와 달리 데이터 입출력 방법에 있어서 옵션 부여.

#include<sys/types.h>

#include<sys/socket.h>


int recv(int sock, void *buf, int len, unsigned int flags);

int send(int sock, const void * buf, int len, unsigned int flags);


옵션의 종류와 의미

 - MSG_DONTROUTE (send 함수 호출시에만 사용하는 옵션)

    데이터 전송 시 라우팅 테이블(라우터에 있음)을 참조하지 않음.

    => Local network 내에서만 패킷을 전송하겠다는 뜻.

 - MSG_DONTWAIT (send, recv 함수 모두에 사용하는 옵션)

    not blocking 입출력 기능

 - MSG_OOB (send, recv 함수 모두에 사용하는 옵션)

    데이터 전송 시, 긴급 데이터(Out-of-band data) 전송

    ※ Out-of-band : 도로 상의 갓길을 생각하면 좋음.

 - MSG_PEEK (recv 함수 호출시에만 사용하는 옵션)

    버퍼에 데이터 유무 확인.


긴급한 데이터의 전송

전송 프로그램 예시

write(sock, "123", 3);

send(sock, "4", 1, MSG_OOB);

write(sock, "567", 3);

send(sock, "890", 3, MSG_OOB);


수신 프로그램 예시

① fcntl(send_sock, F_SETOWN, getpid());

파일 디스크립터를 제어하는 함수.

send_sock : 소켓의 파일 디스크립터

F_SETOWN 소유자를 설정.

getpid(); : 현재 프로세스의 ID

※ 뜻 : send_sock의 소유자를 현재 프로세스로 설정한다.
※ 사용 이유 : 어느 프로세스에게 시그널을 발생시킬지 정해야 한다. SIGURG를 받기 위해 반드시 소켓의 소유자를 지정해야 한다. 


② state = sigaction(SIGURG, &act, 0);

SIGURG : 긴급 데이터(out-of-band)를 받을 때 발생하는 시그널.


긴급 데이터 전송 시 생성되는 패킷

recv함수를 호출할 때 urgent point-1 위치부터 1바이트만 읽어들인다.

긴급 데이터 전송의 특징

- 버퍼 상황에 관계 없이 데이터 전송 ( 버퍼가 비어지길 기다리지 않고 바로 전송)

- Nagle 알고리즘 무시


입력버퍼 검사하기

sleep 함수 사용 이유 : 패킷을 온전히 다 받기 위해 사용.

MSG_PEEK : 메세지를 읽어도 소켓의 수신 버퍼에 데이터 내용을 지우지 않는다.

MSG_DONTWAIT : 버퍼가 비어 있지 않아도 바로 리턴 받기 위해서...

※ 두 개의 옵션을 같이 사용한다.


13-2. readv & writev 입출력 함수

데이터를 모아서 전송(writev) : 한 번 호출하여 한 번에 보낸다.

#include<sys/io.h>

int writev(int fd, const struct iovec *vector, int count);

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

데이터 여러 개(count개)를 모아서 전송한다.

fd : 데이터 전송의 목적지를 나타내는 소켓의 파일 디스크립터

vector : iovec 구조체 이름을 인자로 전달. 전송하고자 하는 데이터에 대한 정보가 담겨진다. 

count : iovec 구조체 변수의 개수.


데이터를 분산 수신(readv) : 한번 호출하여 나누어진 버퍼에 저장한다.

#include<sys/io.h>

int readv(int fd, const struct iovec *vector, int count);

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

fd : 데이터를 수신할 파일 디스크립터

vector : iovec 구조체 이름을 인자로 전달. 어디에 얼마칸큼 데이터를 수신할 것인지 정보를 전달.

count : iovec 구조체 변수의 개수.


배열을 정의하기 위한 구조체의 선언

struct iovec{

    ptr_t iov_base;    // 보내고자 하는 데이터의 시작주소

    size_t iov_len;    // 보내고자 하는 데이터의 크기

};


readv&writev 함수의 적절한 사용

1. 함수호출 회수가 적으면 효율적임.

2. nagle알고리즘을 적용하지 않는 경우 다음과 같은 장점을 가진다.

일반 write 3번 호출 : 3개의 패킷을 생성하여 전송

nagle알고리즘을 적용한 경우

writev 호출 : 1개의 패킷을 생성하여 전송.

3. 큰 배열을 생성하여 하드코딩해서 보내는 것보다 한번의 함수로 모아서 보내는 게 편리하다.


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

윈도우즈 기반 OOB 데이터 송수신

1. 윈도우즈 기반에서는 'signal 핸들링' 이라는 개념이 존재하지 않는다.

2. select 함수는 소켓을 통해서 예외 상황(OOB 데이터 수신)이 발생하였는지 확인 가능하다.

예시)

FD_ZERO(&except);

FD_SET(hSendSock,&except);

exceptCopy = except;

select(?, ?, ?, &exceptCopy, ?);


반응형
반응형

12-1. I/O 멀티플렉싱 기반의 서버

하나의 전송로를 여러 사용자가 동시에 사용해서 효율성을 극대화 하는 것. 전송로를 공유.

전송로를 공유하기 위해서는 서로 약속이 필요하다.

TDMA : 자료 전송 시간을 일정한 시간 폭으로 나누어서 함께 공유하는 방식

FDMA : 주파수 대역 폭을 나누어서 함께 공유하는 방식


I/O 멀티플렉싱

클라이언트와의 입/출력을 담당하는 프로세스를 하나로 묶어버리는 것.

프로세스 = 고속의 전송로.


multitasking(멀티태스킹) 예시


multiplexing(멀티플렉싱) 예시

※ 데이터 입출력 및 접속 모두를 하나로 묶어서 처리한다.

※ 멀티플렉싱 서버가 멀티태스킹 서버보다 성능이 좋다. -> context switching이 없음.


멀티 프로세스 vs 멀티 플렉싱

멀티 프로세스

클라이언트와 서버간의 송수신 데이터 용량이 큰 경우.

송수신이 연속적으로 발생하는 경우에 적합.

멀티 플렉싱

클라이언트와 서버간의 송수신 데이터 용량이 작은 경우.

송수신이 연속적이지 않은 경우에 적합. (예: 채팅)

멀티 프로세스 기반의 서버에 비해 많은 수의 클라이언트 처리에 접함.


12-2. Select 함수 사용하기

파일 디스크립터의 설정

지정된 파일 디스크립터의 변화를 확인한다.

파일 디스크립터 변화 : 파일 디스크립터를 통해 데이터 송수신이 가능한 상태

예시)


1. 파일 디스크립터 설정

관찰하고자 하는 파일 디스크립터를 모아놓는다. (총 3묶음)

2. 파일 디스크립터의 변화(3가지 종류 존재)

입력 버퍼의 변화 : 수신할 데이터가 존재하는가? (입력 버퍼에 데이터 존재)

출력 버퍼의 변화 : 데이터 전송이 가능한 상태인가? (출력 버퍼에 충분한 여유공간 존재)

예외(OOB)상황 관련 버퍼의 변화 : 소켓에서 예외상황이 발생 하였는가?

3. fd_set 자료형

파일 디스크립터를 구분 지어 모아두기 위한 자료형(비트단위 배열)


파일 디스크립터 검사 범위 설정
매크로 함수

FD_ISSET(int fd, fd_set *fdset);
fdset에서 해당 fd(번호)의 비트가 1인지 확인한다. 1이면 TRUE, 0이면 FALSE.

검사해야 할 파일 디스크립터의 개수(범위)를 지정해 준다.

(select 마지막에 리턴된 디스크립터 값에) 가장 큰 파일 디스크립터 값에 1을 더해서 인자를 전달.


Timeout(타임아웃) 설정

Timeout : 정해 놓은 시간이 지나면 (select)함수가 리턴하게 한다.
struct timeval
{
    long tv_sec;    // 초단위
    long tv_usec;  // 마이크로초단위 1/10^6
};

select함수
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

int select(int n, fd_set * readfds, fd_set *writefds, fd_set * exceptfds,struct timeval * timeout);
리턴 : 오류 발생이면 -1, 타임아웃이면 0, 변화가 발생하면 0이 상에 상태가 바뀐 파일 디스크립터 수.
n : 파일 디스크립터 개수(범위) 지정.
readfds : 입력버퍼(수신)할 데이터가 있는지 검사.
writefds : 출력버퍼가 충분히 여유가 있는지 검사.
exceptfds : OOB에 대한 검사. ※ 추후설명
timeout : 타임아웃 설정관련 인수.


select함수를 호출하기 전에는 fd_set에 검사하기 원하는 index의 비트를 1로 세팅해 놓는다.
예시) FD_SET(0, &read_s);    // standard input에 대한 디스크립터를 1로 세팅.
select함수가 리턴이 되면 fd_set에 검사한 결과에 대한 index의 비트만이 1로 세팅되어 있을 것이다.
※ select함수 호출 전과 호출 후에 값이 바뀌기 때문에 select함수를 호출할 때마다 초기화할 수 있도록 원본 fd_set을 복사하여 사용한다.
※ timeout도 select함수 호출 전과 호출 후에 값이 바뀌기 때문에 루프 시작할 때 값을 새로 초기화 주는 것이 좋다.

12-3. 멀티플렉싱 서버의 구현
FD_SET(serv_sock, &reads);
※ 서버소켓으로 오는 입력버퍼(데이터)의 내용이 중요하다. 그 내용은 클라이언트로부터 들어오는 연결요청(일종의 데이터이기 때문)이다.
fd_max : 검사할 디스크립터 개수(범위)를 저장하기 위한 값. 0~fd_max+1의 범위로 설정한다.

12-4. 윈도우즈 기반으로 구현하기
select 함수와 timeval 구조체는 선언된 형태와 기능이 리눅스와 같다.
int select(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout);

※ fd_set 자료형은 기능이 리눅스와 같지만 선언형태는 다르다.
typedef struct fd_set{
    u_int fd_count;                                 // 얼마나 많이 설정되어 있는가?
    SOCKET fd_array[FD_SETSIZE];      // SOCKET의 배열
}fd_set;
※ 소켓의 핸들 값이 일정하지 않고 윈도우즈에는 리턴된 핸들 값을 기억하는 것이 편하다.
그러므로 소켓 핸들의 개수를 fd_set 내부적으로 보관하고 있다.


※ 다음 매크로는 리눅스와 사용 방법은 거의 같다.
FD_CLR(s, *set)
:set 포인터가 가리키는 배열에서 핸들 s를 삭제한다.
FD_ISSET(s, *set)
:핸들 s가 set포인터가 가리키는 배열의 멤버라면 TRUE를 아니면 FALSE를 리턴한다.
FD_SET(s, *set)
:핸들 s를 set포인터가 가리키는 배열의 멤버에 포함시킨다.
FD_ZERO(*set)
:set 포인터가 가리키는 배열을 0으로 초기화한다. 

select(0, &temps, 0, 0, &timeout)
※ 윈도우즈 기반에서는 nfds인수가 필요없다.

반응형
반응형

11-1. 프로세스간 통신(Inter-Process Communication)

프로세스간 통신의 정의

독립된 프로세스 간에 데이터를 주고 받는 행위를 의미

프로세스간 통신의 문제점

독립된 프로세스는 (특정 시스템콜을 하지 않는한) 공유 메모리가 존재하지 않기 때문에 데이터를 주고 받는 것이 불가능.

해결책 : 독립된 프로세스들이 통신할 수 있도록 '파이프'를 제공.


파이프 생성함수

#include <unistd.h>

int pipe(int fd[2]);

파이프는 커널 객체이다. fd[1]은 파이프 입구, fd[0]은 파이프 출구로 사용한다.

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

파이프의 생성과 프로세스간 통신


파이프의 특징

1. 파이프는 fork 함수에 의해서 복사되지 않는다.

-> 파이프의 입출력을 의미하는 파일 디스크립터가 복사되는 것.

2. 파이프는 방향성이 존재하지 않는다.

-> 파이프의 입구와 출구만 있다. (자신이 쓰고 읽을 수 있는 문제 발생 가능)


양방향 통신을 위한 파이프의 생성

하나의 파이프는 하나의 용도로만 사용한다.

예시) A->B 파이프, B->A 파이프


11-2. 가위 바위 보 게임 구현하기

가위 바위 보 게임 구조

1단계 : 파이프 생성

2단계 : 자식 프로세스 생성

3단계 : (서버, 클라이언트 모두) 데이터 입력(가위 바위 보 선택)

4단계 : 파이프를 통해 데이터 전송(자식->부모)

5단계 : 승자 확인!

6단계 : 결과 전송(부모->자식)

반응형
반응형

10-1 다중 접속 서버의 구현 방법들

※ 클라이언트의 (여러명)다중 접속을 허용(concurrent server).

리눅스 기반의 다중 접속 서버 구현 방법들

1. 프로세스 생성을 통한 멀티태스킹(Multitasking) 서버의 구현

2. select 함수에 의한 멀티플렉싱(Multiplexing) 서버의 구현

3. 쓰레드 기반으로 하는 멀티쓰레딩(Multithreading) 서버의 구현


10-2. 프로세스의 생성

프로세스에 대한 이해

1. 실행되고 있는 프로그램의 기본 단위

2. 생성된 프로세스는 운영체제에 의해 할당된 고유한 ID를 가진다.

3. 하나의 프로그램 내에서 여러 개의 프로세스가 동시에 실행 될 수 있다.


ps -u : 어떤 프로세스가 수행되고 있는 지 보여준다. (shell 명령)

pid : 프로세스 아이디


fork 함수 호출을 통한 프로세스 생성


#include<sys/types.h>

#include<unistd.h>


pid_t fork(void);

성공시 프로세스 ID(자식프로세스면 0), 실패시 -1


프로세스 생성 예제

소스내용

a=10;

(... 중략 ...)

pid = fork();

// fork()를 호출하자마자 메모리 상태.

(... 중략...)

printf("fork 성공, 프로세스 id : %d\n",pid);

if(pid==0)

    data +=10;

else

    data -=10;

printf("data : %d",pid);

(...중략...)


10-3 프로세스&좀비(Zombie) 프로세스

좀비 프로세스 : 프로세스 종료후 메모리 상에서 사라지지 않는 프로세스

첫번째 그림

자식 프로세스는 부모 프로세스가 (종료)리턴 값을 받을 때까지 메모리 상에서 사라지지 않는다.

(죽지 않는다.)


두번째 그림

자식 프로세스가 (종료)리턴해 준 값을 커널이 부모 프로세스에게 주고 자식 프로세스를 종료한다.

부모 프로세스는 자식 프로세스의 (종료)리턴 값을 달라고 요구해야 좀비 프로세스가 생성되지 않는다.


※ int main()에서 return 0;하는 이유

프로그램의 main은 프로세스 생성을 요구한다.

리턴의 의미는 커널이 제대로 종료했는 지 알기 위해 필요한 값이다.


좀비 프로세스의 생성 이유.

자식 프로세스는 부모 프로세스에게 실행 결과에 대한 값을 반환해야 한다.

부모 프로세스가 반환받지 않으면 자식 프로세스는 좀비 프로세스가 된다.


wait함수의 사용

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int *status);

자식 프로세스를 기다린다. 자식 프로세스가 종료하지 않으면 무한 대기할 수도 있다.

리턴 : 성공시 자식 프로세스 ID, 실패시 -1

status : 다음 매크로 함수 이용

WIFEXITED(status) : 정상 종료시 0을 반환

WEXITSTATUS(status) : 종료시 return하거나 exit함수의 인자로 넘겨진 값을 반환한다.


좀비 프로세스 소멸

소멸 방법 : 부모 프로세스에서 자식 프로세스의 반환 값을 요구한다.


waitpid함수의 사용

#include<sys/types.h>

#include<sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

참고주소 : http://linux.die.net/man/2/waitpid

리턴 : 성공시 종료된 자식 프로세스 ID, 실패시 -1

option이 WNOHANG이고 상태가 변한 프로세스가 없으면 0을 리턴.

pid 

-1이면 임의의 자식 프로세스가 종료하기를 기다리게 된다.

0이면 호출한 프로세스의 아이디와 같은 그룹일 경우.

0이상이면 기다리길 원하는 자식 프로세스의 ID. 

status : wait 함수의 status와 동일.

WNOHANG : 종료한 자식 프로세스가 없는 경우 대기 상태로 들어가지 않고 바로 리턴.


10-4. 시그널(Signal) 핸들링 & 좀비(Zombie) 프로세스

어떻게 효율적으로 좀비 프로세스를 처리할까?

부모 프로세스 입장에서 좀비(자식) 프로세스가 발생하는 시점을 모르기 때문에 (시스템은 알고 있음.)

운영체제가 시그널을 보내 부모 프로세스가 그 시그널을 읽어서 (시그널 핸들러를 통해) 자식 프로세스를 처리.


시그널

시스템 내의 특정상황 발생을 알리기 위해서 커널이 전달하는 신호. (운영체제에 의해 약속된 상수.)

시그널 핸들러

적절한 처리를 해 주는 함수

시그널 핸들링

시그널이 발생함에 따라 이에 대한 적절한 처리를 해 주는 것.


1. 자식 프로세스가 종료되었을 때(특정 상황) 부모 프로세스에게 시그널을 보냄.

2, 부모 프로세스는 시그널을 받으면 시그널 핸들러를 실행


signal 함수를 이용한 시그널 핸들링

signal함수

#include<signal.h>

void (*signal(int signum, void (*func)(int)))(int);

시그널(signum)과 시그널 핸들러(func)를 연결시켜주는 기능.

(해당 시그널 번호를 받으면 시그널 핸들러 함수가 실행된다.)

리턴이 void (*)(int); 함수 포인터 타입

리턴 : 오류시 SIG_ERR

signum

 시그널 발생상황 
 SIGALRM 시간을 예약해 놓고 그 시간이 되었을 경우 발생한다. 
 SIGINT Ctrl-C를 누를 경우 인터럽트 발생 
 SIGCHLD 자식 프로세스가 종료된 경우 발생. 


※ 시그널 핸들러에서 signal함수를 넣는 이유

운영체제가 시그널을 등록하면 딱 한 번만 signal함수를 호출하는 경우가 있기 때문에

※ signal로 시그널을 등록하지 않으면 운영체제에서 기본적으로 정의한 signal 핸들러가 호출된다.


sigaction함수

#include<signal.h>

시그널(signum)과 시그널 핸들러(func)를 연결시켜주는 기능. 이 함수를 사용하는 것을 추천.

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);


struct sigaction

{

    void (*sa_handler)(int);

    sigset_t sa_mask;    

    int sa_flags;

};


sa_mask

동시에 signal이 발생해도 처리할 수 있도록 한다. 

pending된 signal을 block, unblock시키는 용도로 사용.


sigemptyset : 시그널 마스크 비트(sa_mask)를 0으로 설정.


10-5. fork 함수를 이용한 다중 접속 서버의 구현


1. 클라이언트가 서버에게 연결 요청

2. 서버 프로세스가 연결 요청을 받을 때마다 자식 프로세스 생성 -> 자식 프로세스는 부모 프로세스가 전달한 디스크립터(client_sock)을 받는다.

3. 클라이언트와 자식 프로세스와 통신

※ 소켓은 운영체제가 관리하기 때문에 fork를 해도 복사되지 않는다. 파일 디스크립터는 복사가 된다.


파일 디스크립터의 복사

하나의 소켓에 대한 파일 디스크립터가 둘 이상 존재하는 경우, 모든 파일 디스크립터를 종료해 줘야 해당 소켓이 종료된다.

※ 커널에서 소켓은 참조카운트를 가지고 있을 듯...


부모 프로세스는 클라이언트 소켓이 필요없고 자식 프로세스는 서버 소켓이 필요없으므로 복제된 후 미리 삭제해 준다.


10-6. TCP 입출력 루틴(Routine) 분할하기

에코 클라이언트의 경우 적용 가능(그냥 하나의 모델로서... 소스가 복잡해 지는 단점이 있다.)

※ 송수신이 잦은 프로그램에 한해 효율성이 증가할 수 있다.


입력과 출력을 실행하는 루틴을 부모-자식 프로세스로 분리한다.

=> 입출력이 독립되어 다음과 같은 통신이 가능하다.


반응형
반응형

9-1 소켓의 옵션

1. 소켓의 옵션 조작 : 소켓의 기본적인 특성(다양함)을 변경하는 것.

※ 운영체제에 의해 관리. 운영체제는 소켓의 특성에 대한 정보를 보관하고 있다.

2. 소켓의 기본적인 특성

 - 입력 및 출력 버퍼의 크기

 - 데이터 전송 방식(TCP 또는 UDP)

 - TTL(Time To Live) : 건너뛸 수 있는 라우터의 개수. hop과 관련됨.

3. 옵션은 대부분 변경(set) 가능하지만 참조(get)만 가능한 것도 있다.


소켓의 옵션 관련 함수

1. 현재 설정 상태 정보를 가져오는 함수.

#include <sys/types.h>

#include <sys/socket.h>

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

sock : 소켓을 나타내는 디스크립터

level : 확인할 옵션의 프로토콜 레벨

optname : 확인할 옵션의 이름

optval : 원하는 옵션의 이름의 값을 얻음.

optlen : optval 포인터가 가리키는 버퍼의 크기.


#include<winsock2.h>

int getsockopt(SOCKET s, int level, int optname, char FAR *optval, int FAR *optlen);

s : 소켓 핸들


2. 옵션을 변경하는 함수.

#include<sys/types.h>

#include<sys/socket.h>

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

sock : 소켓을 나타내는 디스크립터

level : 변경할 옵션의 프로토콜 레벨

optname : 변경할 옵션의 이름

optval : 원하는 옵션의 값을 저장한 포인터 전달.

optlen : optval 포인터가 가리키는 버퍼의 크기.


#include<winsock2.h>

int setsockopt(SOCKET s, int level, int optname, const char FAR * optval, int optlen);

s : 소켓 핸들


소켓의 옵션

Protocol Level(프로토콜 레벨) : 옵션이름을 그룹짓기 위해 필요.

SOL_SOCKET : 일반적인 소켓의 옵션.

IPPROTO_IP : IP에 대한 소켓의 옵션.

IPPROTO_TCP : TCP에 대한 소켓의 옵션


※ SO_TYPE 옵션 : 소켓의 타입 정보(TCP(1)/UDP(2))값을 얻는다. 다시 재설정할 수 없다.

윈도우즈용 프로그램


9-2 SO_SNDBUF & SO_RCVBUF

getsockopt함수를 통해서 디폴트 버퍼의 크기를 구할 수 있다.

※ 리눅스 시스템 마다 입력 버퍼, 출력 버퍼의 크기는 다르다.

내 컴퓨터 우분투의 경우

입력버퍼 : 87380

출력버퍼 : 16384


setsockopt함수를 통해서 버퍼의 크기를 변경할 수 있다.

※ 바꿀 수 있는 최소 버퍼의 크기는 정해져 있다

※ 버퍼가 없으면 TCP를 할 수 없기 때문에 0으로 설정해도 최소값으로 바뀌어서 버퍼의 크기가 설정된다. 성능과 직결되는 문제이다.


9-3. SO_REUSEADDR

TIME_WAIT 상태?

연결 종료시 마지막 패킷 전송 실패를 대비하기 위한 상태


종료를 먼저 요청한 호스트가 나중에 소켓을 소멸시킨다. 

마지막 ACK 신호가 잘 전달되지 않았을 때를 대비해서 TIMEOUT이 존재한다.

이유 : 상대방(B)의 종료 요청(FIN)을 기다리는 데 걸리는 TIMEOUT이다.


서버의 연결 종료

서버에서 먼저 종료 요청 후, 다시 서버를 가동시키면?

서버가 종료 메세지를 보내고 TIMEOUT 시간동안은 서버 재가동 불가. 

이유 : 그 아이피와 포트가 사용중이기 때문.


TIME_WAIT 타이머의 재시작

TIME-WAIT 상태는 우리의 생각보다 더 길어 질 수 있다.


마지막 ACK신호를 못 보낼 경우 다시 FIN을 보낼 때 다시 TIMEOUT을 설정해야 되기 때문이다.


※ TIMEOUT이 걸린 소켓이라고 그 주소와 포트에 다시 소켓을 만들 수 있게 설정한다.

server_socket = socket(PF_INET, SOCK_STREAM, 0);

option = sizeof(option);

option = TRUE;

setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));


9-4. TCP_NODELAY

네트워크 상의 패킷 수를 줄이기 위해 제안된 알고리즘

ACK을 수신해야만 다음 전송을 진행하는 알고리즘.

※ ACK이 올 때까지 보낼 내용은 버퍼에 쌓아 둔다. (전송버퍼가 필요한 이유)

예시) "agle"은 버퍼에 쌓여 있다가 보내진다.

※ nagle 알고리즘이 off면 데이터가 오면 무조건 상대방에게 보낸다.


Nagle 알고리즘의 장점과 단점

장점 : 네트워크의 효율성이 높아진다. (적은 패킷의 양)

단점 : 전송 속도가 느리다. (ACK 수신 후 패킷 전송)

생각해 볼 문제 : Nagle 알고리즘의 중단이 데이터 전송 속도를 무조건 향상 시켜주는 것은 아니다.

※ 네트워크 트래픽 양이 많다면 (차가 많으므로)속도가 꼭 빨라지지는 않을 수 있음.


TCP_NODELAY

serv_sock = socket(PF_INET, SOCK_STREAM, 0);

opt_val = TRUE;    // option 값이 TRUE면 nagle알고리즘 off 됨.

setsockopt(serv_sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, sizeof(opt_val));

반응형
반응형

8-1. DNS(Domain Name System)

1) 도메인 이름이란? 

1. 영문으로 표현되는 계층적 주소 체계 방식.

※ IP 이후에 도메인이 나왔음. 중복되면 안 된다.

2. 각 나라마다 존재하는 Network Information Center에서 관리. => 약속

한국은 KRNIC(http://www.nic.or.kr, Korea Network Information Center)에서 담당.

3. 도메인 이름 = 호스트 이름 + 도메인 이름

예시) academy.freelec.co.kr = academy + freelec.co.kr

4. TCP/IP는 도메인 이름을 인식 못함.

※ IP와 도메인의 중계 역할을 하는 서버 : 도메인 서버


현실세계의 주소체계


인터넷 상에서의 주소체계

root 도메인

com : 회사, zw : 짐바브웨, kr : 한국

2차 도메인

co : 나라 안의 기업, ac : 나라 안의 대학, or : 행정기관, 


2) DNS 서버

1. 모든 도메인 이름은 해당 IP와 함께 DNS 서버에 등록되어야 한다.

※ DNS 서버는 테이블 형식으로 정보를 가지고 있다.

2. 도메임 이름을 IP 주소로 변환해 주는 작업을 한다.


1. host는 DNS 서버에 도메인 주소 질의

2. DNS 서버는 테이블 정보를 검색하여 IP를 답변해 준다.

※ 분산 데이터베이스 : 모르면 다른 DNS 서버에 물어본다.

3. host는 받은 IP정보를 통해 원하는 도메인 주소로 접속한다.


도메인 이름과 IP변환 과정

※ DNS 서버는 계층적 구조로 되어 있다. 모를 경우 부모에게 물어본다. 

루트 DNS는 모든 주소에 대한 정보를 어느 자식에 있는 지 알고 있다. (자손까지는 아님.)


8-2 IP 주소와 도메인 이름 사이의 변환

변환(도메인 이름 <-> IP주소)의 필요성

상대적으로 잦은 IP 변화에 대한 능동적인 문제 해결

※ IP를 하드코딩하면 IP주소가 바뀌었을 때는 접속할 수 없기 때문에 도메인 이름을 하드 코딩하여 IP가 바뀌더라도 도메인 이름으로 접속할 수 있도록 한다.

※ 한 도메인에 대해 IP를 여러 개 두고 상황에 따라 분산하여 접속할 수 있게 한다.


도메인 이름->IP주소 함수

※ 함수 자체가 DNS 서버에게 물어보고 IP주소등의 정보가 리턴된다.


#include<netdb.h>

struct hostent* gethostbyname(const char* name);

name : 도메인 이름 문자열


#include<winsock2.h>

struct hostent FAR *gethostbyname(const char FAR *name);

name : 도메인 이름 문자열


struct hostent 구조체 


struct hostent

{

char *h_name;

char **h_aliases;

int h_addrtype;

int h_length;

char **h_addr_list;

};


공식 이름(official name)

별명 목록(alias list)

호스트 주소 형태(host address type)

주소의 길이(IPV4 : 4, IPV6 : 16)

주소 목록(list of addresses)

struct hostent

{

char FAR * h_name;

char FAR * FAR * h_aliases;

short h_addrtype;

short h_length;

char FAR * FAR * h_addr_list;

}


※ IP주소를 일반화 하기 위해 char*를 사용하였다. IN_ADDR 구조체 형태로 IP주소가 저장되기 때문에 void* 포인터 형태가 더 좋았을 듯.


struct hostent 구조체 변수


반응형
반응형

※ 연결에 대한 내용이므로 TCP/IP 프로토콜에 대한 이야기.

※ 우아한 종료 = graceful close. (MSDN 참고)


7-1 소켓 연결 종료의 문제점

2개의 가상의 통로가 생성 (각 통로는 단방향)


스트림 : 연결된 상태 (데이터를 주고 받을 수 있도록 연결된 시스템의 내부적인 상황)

입력 스트림 : 데이터 수신을 위한 스트림

출력 스트림 : 데이터 전송을 위한 스트림


※ TCP 소켓은 데이터를 전송하면서 동시에 수신도 가능하다.


소켓 연결 종료의 문제점

1. close, closesocket 함수 호출 : 입력, 출력 스트림 완전 종료

 => 데이터를 송수신 할 수 없다. 데이터가 소멸된다.

2. 일방적인 방식의 완전 종료는 경우에 따라서 문제가 될 수 있다.



7-2 우아한 소켓의 연결 종료

half close : 입력 혹은 출력 스트림 중 하나의 스트림만 종료하는 행위

예시) A 호스트의 출력 스트림만 종료

#include <sys/socket.h>

int shutdown(int s, int how);

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

s : 종료하고자 하는 소켓의 파일 디스크립터

how : 종료 모드를 인자로 전달한다.

 상수값 모드 정의 
 0  SHUT_RD  입력 스트림 종료 
 1  SHUT_WR 출력 스트림 종료 
 2  SHUT_RDWR  입출력 스트림 종료


출력 스트림의 종료의 필요성

※ Thank you를 받기 위해 shutdown함수를 사용한다.


1. 출력 스트림을 종료하게 되면, 연결되어 있던 호스트로 EOF 메세지 전달.

※ 출력 스트림을 종료하는 방법 : close(closesocket), shutdown 함수.

2. EOF 전송으로 데이터 전송의 끝을 알려 줄 수 있다.

3. EOF 전송 시, 상대 호스트의 데이터 수신 함수(read, recv)는 0을 리턴.


※ EOF를 생략하고 보내면 서버도 read() 무한 대기, 클라이언트도 파일의 끝을 모르므로 read() 무한대기.


※ EOF의 값 : -1 (int type) -> 1바이트를 4바이트로 casting하면 -1이 아닐 수 있다.


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

※ 윈도우즈 기반에서는 파일 열기를 위해 표준 라이브러리를 사용하였다.


#include <winsock2.h>

int shutdown(SOCKET s, int how);

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

s : 종료하고자 하는 소켓의 파일 디스크립터

how : 종료 모드를 인자로 전달한다.

 상수값 모드 정의 
 0  SD_RECEIVE 입력 스트림 종료 
 1  SD_SEND 출력 스트림 종료 
 2  SD_BOTH 입출력 스트림 종료

반응형
반응형

※ TCP와 UDP를 특징을 비교하면서 공부하는 것이 좋다.

6-1. UDP의 이해

1. IP를 기반으로 데이터를 전송한다. (TCP와 공통점)

2. 흐름제어(flow control)을 하지 않기 때문에 데이터 전송을 보장 받지 못한다. (TCP와 차이점)

3. 연결설정 및 연결 종료 과정도 존재하지 않는다. (TCP와 차이점)

4. 연결 상태가 존재하지 않는다. (TCP와 차이점)


UDP의 역할

포트 정보에 의한 프로세스의 구분

UDP 패킷 = 데이터 그램(Datagram)


6-2. UDP 기반 서버/클라이언트의 구현

※ 소켓을 생성하고 bind까지만 하면 바로 입출력함수를 사용하면 된다. 

(socket~bind : 서버, socket : 클라이언트)

일반적으로 연결 설정 과정을 거치지 않는다.

데이터를 주고 받기 위한 소켓(우체통에 비유)은 하나만 생성해도 된다. (패킷은 우편)


※ UDP는 비연결 프로토콜이기 때문에 주소 정보를 알아야 한다.

데이터 전송함수

int sendto(int sock, const void* msg, int len, unsigned flags, const struct sockaddr *addr, int addrlen);

int sendto(SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen);

1~4번째 인자는 TCP send함수와 비슷.

addr : 목적지 주소 정보

addrlen : 목적지 주소 정보 구조체 크기


데이터 수신함수

int recvfrom(int sock, void *buf, int len, unsigned flags, struct sockaddr *addr, int *addrlen);

int recvfrom(SOCKET s, char FAR *buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen);

1~4번째 인자는 TCP recv함수와 비슷.

addr : 목적지 주소 정보

addrlen : 목적지 주소 정보 구조체 크기


※ uecho_client_win.c : 윈도우 UDP 에코 클라이언트는 리눅스 용과 다르게 제작되어 있음.

※ UDP에서 클라이언트의 주소는 언제 할당될까? 6-4에 있음.


6-3. 데이터의 경계(boundary)가 존재하는 UDP 소켓

UDP 소켓은 데이터를 송수신하는데 필요한 함수 호출의 수를 정확히 일치 시켜야 한다.


6-4. connect 함수 호출을 통한 성능의 향상

1. TCP 소켓에서의 connect 함수의 의미

 - IP와 포트의 할당

 - Three-way handshaking

2. UDP 소켓에서의 connect 함수의 의미

 - IP와 포트의 할당

※ connect함수가 없으면 sendto 함수가 제일 처음 호출되는 시점에 IP와 Port가 할당된다. 

클라이언트에서 IP와 Port가 한 번 할당되면 close()함수 호출시까지 변하지 않는다.


TCP/UDP 소켓에서 공통적으로 가지는 connect의 의미


connect함수의 의미 

커널과 소켓이 논리적으로 연결하고 그것을 유지한다.


connect함수를 호출하지 않을 때 UDP 클라이언트의 데이터 송수신

1. sendto, recvfrom 함수를 호출하면 커널과 소켓이 연결되고 소켓과 호스트와 통신할 준비를 한다.

2. 호출이 끝나면 커널과 소켓의 연결이 해제된다.

※ 위의 1~2를 반복하는 데 많은 시간을 잡아먹고 커널과 소켓을 연결했다 해제했다 하기 때문에 성능 효율이 떨어진다.


connect 함수 호출이 주는 이점

1. 데이터를 주고 받는 속도가 빨라진다.

2. TCP 소켓 기반의 데이터 입출력 함수를 그대로 사용할 수 있다.


반응형

+ Recent posts