프로세스들은 독립된 메모리 영역을 지니기 때문에 지금까지 구현해 온 멀티 프로세스 서버의 경우 프로세스간에 데이터를 주고 받을 수 없다는 단점이 있음
그러나 프로세스간에 데이터를 주고 받아야 하는 경우가 발생될 수 있음
→ 시스템 레벨에서 프로세스간 통신을 가능하게 하는 방법을 제시하고 있음
#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)