2016년 5월 22일 일요일

뇌자극 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) 프로세스간의 통신 문제 : 부모 프로세스와 자식 프로세스가 모두 독립되어 있다 보니 데이터 교환이 쉽지 않다. 

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


댓글 없음:

댓글 쓰기