네트워크 프로그래밍 10 멀티프로세스 기반의 서버 구현

zh025700·2022년 4월 13일
1

네트워크 프로그래밍


10. 멀티프로세스 기반의 서버 구현

프로세스 ID

  • 프로세스는 OS로부터 ID를 부여받음 = 프로세스 ID

fork()를 통한 프로세스의 생성

pid_t fork();
  • 성공 시 프로세스 ID, 실패시 -1 반환

      fork는 호출한 프로세스의 복사본을 생성한다
      이미 실행 중인 프로세스를 fork를 통해 복사한다.
      이후 fork 함수 반환 이후의 문장을 실행

fork의 특징

  • 부모 프로세스(원본)
    • fork의 반환값은 자식 프로세스의 ID
  • 자식 프로세스(복사본)
    • fork의 반환값은 0

좀비 프로세스

프로세스를 생성했으면 소멸시키는것도 중요하다
제대로 소멸 안시키면 남아서 시스템의 자원을 사용한다 => 시스템에 부담
  • 자식이 종료되는 경우

    • 인자를 전달하면서 exit를 호출

    • main에서 return을 실행하며 값을 반환

      반환값 모두 OS에 전달됨, 근데 이 값이 부모로 전달될때까지 자식을 안죽임
      이때 남아있는 프로세스 => 좀비프로세스라 부름

좀비는 언제 소멸??

  • 부모에게 exit의 인자나 return 문의 반환값이 전달 될때

      그럼 부모가 값을 안받는다면.. 자식은 좀비네??

부모는 반환값을 받을 준비를 해야한다!

부모가 종료되면 좀비 자식도 죽는다
근데 일반 자식(좀비아님)은 안죽는다

wait()의 사용

pid_t wait(int* statloc);
  • 성공 시 종료된 자식 프로세스의 ID, 실패시 -1 반환

  • 종료된 자식이 있으면 자식이 종료되면서 전달한 값(exit, return 값)이 매개변수로 전달된 주소의 변수에 저장됨

변수에는 자식이 종료되면서 전달한 값 이외의 정보가 포함되어 있으니, 값의 분리를 거쳐야함

  • WIFEXITED(status)
    • 자식이 정상 종료(exit, return)하면 true 반환
  • WEXITSTATUS(status)
    • 자식의 전달 값 반환 -> status에

만약 wait이 호출되었는데 종료한 프로세스가 없다면, 자식이 종료될때까지 부모가 block 상태가 된다

블로킹을 안하는 함수는 업나..

waitpid의 사용

wait과 달리 블로킹을 유발하지 않는다.
pid_t waitpid(pid_t pid,int* statloc, int options);
  • 성공시 종료된 자식의 ID or 0 실패시 -1 반환
  • pid: 종료를 확인하고자 하는 자식의 pid
    • pid = -1: 임의의 자식 프로세스가 종료되는걸 기다림(wait과 같아짐)
    • pid = 0: waitpid를 호출한 프로세스의 프로세스 그룹 pid와 같은 프로세스 그룹 id를 가진 프로세스 기다림
    • pid > 0: pid가 pid인 자식 기다림
    • pid < -1: 프로세스 그룹 id가 pid의 절댓값과 같은 자식 프로세스 기다림
  • statloc: wait과 같은 의미
  • options
    • WNOHANG: non-blocking, 종료된거 없어도 블록킹 X, 0을 반환
    • WCONTINUED: SIGCONT로 중지된 자식이 재개된 경우 반환

wait, waitpid 호출 전 종료된 자식 프로세스 값이 있다면 함수의 인자에 종료 값이 들어간다

시그널 핸들링

signal

  • 이벤트가 일어나면 프로세스에 알려준다
  • 시그널은 프로세스 간 전달 가능
  • 커널에서 프로세스로 시그널 전달 가능
  • SIGCHLD
    • 자식 프로세스가 종료, 정지 된다면 부모에게 SIGCLD가 보내진다
signal(int signo,void (*func)(int)));
  • signo: 특정 상황의 정보

  • 둘째 파라미터: signal handler

  • signal handler

    • 시그널을 잡기 위해서 signal handler는 signal을 선언하기 전에 존재해야함
unsigned int alarm(unsigned int seconds);
  • 시그널이 발생하기까지 남아있는 시간을 초 단위로 반환

시그널이 발생하면 sleep 함수로 블로킹된 프로세스가 깨어난다

sigaction

int sigaction(int signo,const struct sigaction* act, struct sigaction* oldact);
  • 성공시 0 실패시 -1 반환
  • signo: 시그널 정보의 인자
  • act: 해당 시그널 발생시 호출될 핸들러를 가진 구조체 주소
  • oldact: 이전에 등록되었던 시그널 핸들러의 함수포인터를 얻는데 사용됨, 필요 없으면 0
sigaction을 사용하기 위해서는 sigaction이라는 이름의 구조체 변수를 선언 및 초기화 해야함
struct sigaction{
    void (*sa_handler)(int); // 시그널 핸들러 함수의 포인터
    sigset_t sa_mask;       // 시그널 핸들러가 실행중일때 추가적으로 블럭할 시그널
    int sa_flags;           // 시그널 행동에 영향을 주는 flag
}
  • sa_handler에 시그널 핸들러의 함수 포인터값을 저장
  • sa_mask = 0으로
  • sa_flags = 0으로

시그널 핸들링으로 좀비프로세스 소멸시키는 법

SIGCHLD에 대한 시그널 처리를 하고 해당 핸들러 함수에서 waitpid를 이용해 자식 프로세스의 종료값을 확인

멀티태스킹 기반 다중 접속 서버

프로세스 기반의 다중접속 서버의 구현 모델

동시에 둘 이상의 클라이언트에게 서비스를 제공하는 형태 구현!
클라이언트의 서비스 요청이 있을때 마다 서버는 자식 프로세스를 생성해서 서비스를 제공한다
ex) 5개의 클라이언트라면 5개의 자식을 생성해 서비스를 제공한다
  • 과정
  1. 서버(부모)는 accept를 통해 연결 요청을 수락
  2. 이로 생성되는 소켓파일디스크립터를 자식프로세스를 생성해 넘겨줌(별도의 과정 필요없어!!)
  3. 자식은 전달받은 소켓파일디스크립터를 이용해 서비스를 제공

fork를 하고 부모에선 accept로 만든 서버 소켓을 자식에선 서버 소켓을 close해야한다

    왜??

fork를 통한 파일 디스크립터의 복사

부모 프로세스가 지니던 서버, 클라이언트 소켓의 파일 디스크립터가 fork로 자식에게 복사되었다.
BUT!! 같은 파일 디스크립터가 아니다.
그래서 하나의 소켓에 두개의 파일 디스크립터가 존재하게 된다.

사용하지 않는 소켓을 미리 닫는다.(파일디스크립터를 닫는다)

TCP의 입출력 루틴 분할

지금까지 구현한 데이터 에코 방식은
서버로 데이터 전송 -> 데이터가 에코될때까지 기다림 -> 에코로 돌아온 데이터 수신 -> 다른 데이터를 전송
즉, 한번 데이터를 전송하면 에코되는걸 기다려야함!
이걸 두개의 프로세스로 쪼개 입출력을 나눈다면??
  • 부모 = 데이터 수신을 담당
  • 자식 프로세스 = 송신을 담당

    데이터 수신 여부에 관계없이 데이터를 전송 가능!

인터렉티브 방식의 데이터 송수신을 진행하는 경우에는 이러한 형태의 구현이 어울리는 상황이 있고, 어울리지 않는 상황이 있다.

profile
정리

0개의 댓글