Part1_프로세스간 통신(IPC)

·2023년 11월 27일
0

[프로세스간 통신]

프로세스들은 독립된 메모리 영역을 지니기 때문에 지금까지 구현해 온 멀티 프로세스 서버의 경우 프로세스간에 데이터를 주고 받을 수 없다는 단점이 있음

그러나 프로세스간에 데이터를 주고 받아야 하는 경우가 발생될 수 있음

→ 시스템 레벨에서 프로세스간 통신을 가능하게 하는 방법을 제시하고 있음

[파이프]

#include <unistd.h>

int pope(int fd[2]);

// fd : 크기가 2인 int 배열을 요구하고 있음
// 함수 호출이 끝나면 두 개의 파일 디스크립터가 fd[0]과 fd[1]에 담김
// fd[0] : 파이프의 출구를 의미 (데이터를 입력받을 수 있음)
// fd[1] : 파이프의 입구를 의미 (데이터를 출력할 수 있음)

[파이프 생성 예제]

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

#define BUFSIZE 30

int main(int argc, char** argv)
{
    int fd[2];
    char buffer[BUFSIZE];
    pid_t pid;
    int state;

    state = pipe(fd);
    if(state == -1)
    {
        puts("pipe() error");
        exit(1);
    }

    pid = fork();

    if(pid == -1)
    {
        puts("fork() error");
        exit(1);
    }
    else if(pid == 0)
    {
        write(fd[1], "Good\n", 6);
    }
    else
    {
        read(fd[0], buffer, BUFSIZE);
        puts(buffer);
    }

    return 0;
}

[실행 결과]

파이프는 프로세스에 종속적이지 않음

부모 프로세스가 파이프의 입구를 통해(출력 디스크립터를 통해) 데이터를 전송하게 되면

자식 프로세스는 파이프의 출구를 통해(입력 디스크립터를 통해) 데이터를 수신하게 됨

부모 프로세스가 파이프를 생성해서 부모 프로세스의 메모리 영역에 올려 놓은 것이 아니라

커널에게 파이프 생성을 요구하고 그 결과로 파일 디스크립터 두 개를 얻게 됨

→ 이 두 개의 파일 디스크립터는 부모 프로세스의 메모리 영역에 저장됨

예제에서는 자식 프로세스가 데이터를 전송하고, 부모 프로세스가 데이터를 수신하고 있음

이 방향을 바꾸려면 어떻게 해야할까? → 역할만 바꿔주면 됨

(부모 프로세스가 fd[1]을 통해서 데이터를 전송하고, 자식 프로세스는 fd[0]을 통해서 데이터를 수신하면 됨)

[하나의 파이프 라인을 통해 부모/자식 프로세스의 메시지 주고 받기]

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

# define BUFSIZE 30

int main(int argc, char** argv)
{
	int fd[2];
	char buffer[BUFSIZE];
	pid_t pid;
	int state;

	state = pipe(fd); // 파이프를 생성하고 있음
	if(state == -1)
	{
		puts("pipe() error");
		exit(1);
	}

    pid = fork();
    if(pid == -1)
    {
        puts("fork() error");
        exit(1);
    }
	else if(pid == 0)
	{
		write(fd[1], "Good!", 6);
		// 여기를 주석 처리하고 실행하면, 자식 프로세스는 파이프를 통해서 데이터를 전송 하자마자,
		// 바로 다음 줄에서 파이프로부터 데이터를 수신해버림
		// 즉, 자식 프로세스는 데이터를 파이프로 전송하고 나서 본인이 가져가 버림
		sleep(2);
		read(fd[0], buffer, BUFSIZE);
		printf("자식 프로세스 출력 : %s \n\n", buffer);
	}
	else
	{
		// 자식 프로세스의 sleep이 주석이 되면 부모 프로세스는 데이터가 들어오기를 기다림(블로킹 상태)
		read(fd[0], buffer, BUFSIZE);
		printf("부모 프로세스 출력 : %s \n", buffer);
		write(fd[1], "Really Good", 12);
		sleep(3);
	}

	return 0;
}

[실행 결과]

자식 프로세스의 sleep 함수가 있으면 정상 작동하지만 주석하면 정상적으로 작동하지 않음

→ 파이프를 두 개 생성하게 되면 입/출력을 분리할 수 있으므로 문제를 해결할 수 있음

[파이프를 두 개 생성해서 데이터를 주고 받는 부모/자식 프로세스]

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

#define BUFSIZE 30

int main(int argc, char** argv)
{
	int fd1[2], fd2[2];
	char buffer[BUFSIZE];
	pid_t pid;

	if(pipe(fd1) == -1 || pipe(fd2) == -1)
	{
		puts("pipe() error");
		exit(1);
	}

	pid = fork();

	if(pid == -1)
	{
		puts("fork() error");
		exit(1);
	}
	else if(pid == 0)
	{
		write(fd1[1], "Good!", 6); // 데이터를 전송하기 위해 사용
		read(fd2[0], buffer, BUFSIZE); // 데이터를 수신하기 위해 사용
		printf("자식 프로세스 출력 : %s \n\n", buffer);
	}
	else
	{
		read(fd1[0], buffer, BUFSIZE); // 데이터를 수신하기 위해 사용
		printf("부모 프로세스 출력 : %s \n", buffer); // 데이터를 전송하기 위해 사용
		write(fd2[1], "Really Good", 12);
		sleep(1);
	}

	return 0;
}

[실행 결과]

sleep 함수의 호출이 필요 없게 됨

→ 이처럼 데이터를 프로세스간에 주고 받을 때는 파이프를 두 개 생성하는 것이 일반적임

[가위 바위 보 게임 구현하기]

서버는 대기 상태에 있다가 클라이언트가 접속해 오면 서버와 클라이언트가 서로 가위 바위 보를 하게 됨

클라이언트 대 클라이언트가 아니라 서버와 클라이언트의 대결 게임

결과를 서로에게 전송해 주고 나서 클라이언트 연결은 종료 되고, 서버는 다른 클라이언트의 연결을 기다림

[1단계]

프로세스간 통신을 위해서 두 개의 파이프를 미리 생성해 놓고 클라이언트의 연결 요청을 대기

[2단계]

클라이언트의 연결 요청을 수락하고 클라이언트와 데이터를 송/수신할 자식 프로세스 생성

[3단계]

부모 프로세스는 콘솔로부터 선택을 입력받고, 자식 프로세스는 클라이언트가 전송해 주는 선택을 입력 받음

[4단계]

부모 프로세스가 입력 받은 결과와 자식 프로세스가 입력 받은 결과를 비교하기 위해

자식 프로세스는 파이프를 통해서 클라이언트의 선택을 부모 프로세스에게 전송

[5단계]

부모 프로세스는 파이프를 통해서 클라이언트의 선택을 입력받고, 두 선택을 비교해서 승자를 가림

[6단계]

승부에 대한 결과를 또 다시 다른 파이프를 통해서 자식 프로세스에게 전달하고,

자식 프로세스는 연결되어 있는 클라이언트에게 결과를 전달하고 연결을 종료함

[가위바위보 서버 구현]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>

#define BUFSIZE 100

void error_handling(char* message);
void z_handler(int sig);
int who_win(int a, int b);

int main(int argc, char** argv)
{
	int fd1[2], fd2[2];
	char buffer[BUFSIZE];
	char intro[] = "입력하세요 (가위 : 0, 바위 : 1, 보 : 2) : ";
	char win[] = "축하합니다. 당신이 이겼습니다. \n";
	char lose[] = "안타깝게도 졌네요. \n";
	char no_winner[] = "비겼네요. 승자가 없습니다. \n";

	int serv_sock;
	int clnt_sock;
	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	struct sigaction act;
	int str_len, state, addr_size;
	pid_t pid;

	if(argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if(pipe(fd1) < 0 || pipe(fd2) < 0)
		error_handling("pipe() error");

	act.sa_handler = z_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;

	state = sigaction(SIGCHLD, &act, 0);
	if(state != 0)
		error_handling("sigaction() error");

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(atoi(argv[1]));

	if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
		error_handling("bind() error");
	if(listen(serv_sock, 5))
		error_handling("listen() error");

	while(1)
	{
		addr_size = sizeof(clnt_addr);
		clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &addr_size);
		if(clnt_sock == -1) continue;

		if((pid = fork()) == -1)
		{
			close(clnt_sock);
			continue;
		}
		else if(pid > 0)
		{
			int result;
			puts("연결 생성");
			close(clnt_sock);
			
			write(1, intro, sizeof(intro));
			read(0, buffer, BUFSIZE);
			read(fd1[0], &buffer[1], BUFSIZE - 1);

			result = who_win(buffer[0], buffer[1]);
			if(result == 0)
			{
				write(1, no_winner, sizeof(no_winner));
				write(fd2[1], no_winner, sizeof(no_winner));
			}
			else if(result == 1)
			{
				write(1, win, sizeof(win));
				write(fd2[1], lose, sizeof(lose));
			}
			else
			{
				write(1, lose, sizeof(lose));
				write(fd2[1], win, sizeof(win));
			}
		}
		else
		{
			close(serv_sock);
			write(clnt_sock, intro, sizeof(intro));
			read(clnt_sock, buffer, BUFSIZE);
			write(fd1[1], buffer, 1);
			str_len = read(fd2[0], buffer, BUFSIZE);
			write(clnt_sock, buffer, str_len);

			puts("연결 종료");
			close(clnt_sock);
			exit(0);
		}
	}
	return 0;
}

void z_handler(int sig)
{
	pid_t pid;
	int rtn;

	pid = waitpid(-1, &rtn, WNOHANG);
	printf("소멸된 좀비의 프로세스 ID : %d \n", pid);
	printf("리턴된 데이터 : %d \n\n", WEXITSTATUS(rtn));
}

int who_win(int a, int b)
{
	if(a == b)
		return 0;
	else if(a % 3 == (b + 1) % 3)
		return 1;
	else
		return -1;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

[가위바위보 클라이언트 구현]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUFSIZE 100
void error_handling(char* message);

int main(int argc, char** argv)
{
	int sock;
	char message[BUFSIZE];
	int str_len;

	struct sockaddr_in serv_addr;

	if(argc != 3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	sock = socket(PF_INET, SOCK_STREAM, 0);

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_addr.sin_port = htons(atoi(argv[2]));

	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
		error_handling("connect() error!");

	str_len = read(sock, message, BUFSIZE - 1);
	messgage[str_len] = 0;
	fputs(message, stdout);
	fflush(stdout);

	str_len = read(0, message, BUFSIZE);
	write(sock, message, str_len);

	str_len = read(sock, message, BUFSIZE- 1);
	message[str_len] = 0;
	puts(message);

	close(sock);
	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

[실행 결과]

(server)

(client)

0개의 댓글