IPC 기반 턴 방식 채팅 프로그램

COZYHAMA·2024년 4월 18일
1

Operating System

목록 보기
4/5
post-thumbnail

목표

터미널 창을 두 개 띄우고,
두 창에서 각각 프로그램을 실행 시킨다.
두 프로그램이 실행되어 프로세스 A 와 프로세스 B 가 구동되었다고 할 떄, 두 프로세스가 다음의 동작이 될 수 있도록 프로그래밍 해보자.

프로그램이 실행되면, 각 프로세스들은 다음과 같은 상태가 된다.

[프로세스 A 화면]              [프로세스 B 화면]
입력대기:                         수신대기...

여기서 프로세스 B 는 입력이 불가능하고, 수신 대기만 하고 있다.
만약 프로세스 A 가 문자열을 입력하고 엔터키를 치면, 프로세스 B 에서 해당 문자열이 출력된다.

다음번엔 프로세스 B 가 입력대기 상태, 프로세스 A 가 수신대기 상태가 된다.

[프로세스 A 화면]              [프로세스 B 화면]
입력대기:                         수신대기...
Hi                                   A) Hi

수신대기...                        입력대기:

정리하면,
프로세스 A 에서 키보드 입력으로 "Hi" 를 타이핑하고 엔터키를 치면 프로세스 B 의 화면에 "A) Hi" 가 나온다.

프로세스 B 에서 키보드 입력으로 "Bye" 를 타이핑하고 엔터키를 치면 프로세스 A 의 화면에 "B) Bye" 가 나온다.

이렇게 두 프로세스는 한 번씩 돌아가며 채팅이 가능하다.
채팅 중 누구라도 "exit" 를 입력하면 A, B 프로세스 모두 종료 된다.

해결

Process A 코드(.c)

#include <fcntl.h>    // 파일 제어 함수를 위한 헤더 파일
#include <stdio.h>    // 표준 입출력 함수를 위한 헤더 파일
#include <stdlib.h>   // 일반 유틸리티 함수를 위한 헤더 파일
#include <string.h>   // 문자열 처리 함수를 위한 헤더 파일
#include <sys/stat.h> // 파일 상태 정보를 위한 헤더 파일
#include <unistd.h>   // POSIX 운영 체제 API를 위한 헤더 파일

#define FIFO_A_TO_B "fifo_a_to_b"  // A에서 B로 데이터를 보낼 FIFO 파일의 이름
#define FIFO_B_TO_A "fifo_b_to_a"  // B에서 A로 데이터를 받을 FIFO 파일의 이름

int main() {
    int fd;              // 파일 디스크립터를 저장할 변수
    char readbuf[80];    // 데이터 수신을 위한 버퍼
    char input[80];      // 데이터 송신을 위한 입력 버퍼

    // FIFO 파일 생성
    mkfifo(FIFO_A_TO_B, S_IFIFO|0666); // A에서 B로의 통신을 위한 FIFO 파일 생성
    mkfifo(FIFO_B_TO_A, S_IFIFO|0666); // B에서 A로의 통신을 위한 FIFO 파일 생성

    while (1) {
        // 입력 받기
        printf("입력대기: ");
        fgets(input, sizeof(input), stdin); // 사용자 입력 받기
        input[strcspn(input, "\n")] = 0;    // 입력된 문자열에서 개행 문자 제거

        fd = open(FIFO_A_TO_B, O_WRONLY);   // 쓰기 전용으로 FIFO 파일 열기
        write(fd, input, strlen(input)+1);  // 입력받은 데이터를 FIFO를 통해 전송
        close(fd);                          // 파일 디스크립터 닫기

        if (strcmp(input, "exit") == 0)     // 입력받은 데이터가 exit이면 종료
            break;

        // 수신 대기
        printf("수신대기...\n");
        fd = open(FIFO_B_TO_A, O_RDONLY);   // 읽기 전용으로 FIFO 파일 열기
        read(fd, readbuf, sizeof(readbuf)); // FIFO를 통해 데이터 수신
        printf("A) %s\n\n", readbuf);       // 수신된 데이터 출력
        close(fd);                          // 파일 디스크립터 닫기

        if (strcmp(readbuf, "exit") == 0)   // 수신된 데이터가 exit이면 종료
            break;
    }

    unlink(FIFO_A_TO_B); // FIFO 파일 삭제
    unlink(FIFO_B_TO_A); // FIFO 파일 삭제
    return 0;            // 프로그램 종료
}

Process B 코드(.c)

#include <fcntl.h>    // 파일 제어 함수를 위한 헤더 파일
#include <stdio.h>    // 표준 입출력 함수를 위한 헤더 파일
#include <stdlib.h>   // 일반 유틸리티 함수를 위한 헤더 파일
#include <string.h>   // 문자열 처리 함수를 위한 헤더 파일
#include <sys/stat.h> // 파일 상태 정보를 위한 헤더 파일
#include <unistd.h>   // POSIX 운영 체제 API를 위한 헤더 파일

#define FIFO_A_TO_B "fifo_a_to_b"  // A에서 B로의 통신을 위한 FIFO 파일 이름
#define FIFO_B_TO_A "fifo_b_to_a"  // B에서 A로의 통신을 위한 FIFO 파일 이름

int main() {
    int fd;              // 파일 디스크립터를 저장할 변수
    char readbuf[80];    // 데이터 수신을 위한 버퍼
    char input[80];      // 데이터 송신을 위한 입력 버퍼

    // FIFO 파일이 이미 생성되어 있다고 가정
    while (1) {
        // 수신 대기
        printf("수신대기...\n");
        fd = open(FIFO_A_TO_B, O_RDONLY);   // 읽기 전용으로 FIFO 파일 열기
        read(fd, readbuf, sizeof(readbuf)); // FIFO를 통해 데이터 수신
        printf("A) %s\n\n", readbuf);       // 수신된 데이터 출력
        close(fd);                          // 파일 디스크립터 닫기

        if (strcmp(readbuf, "exit") == 0)   // 수신된 데이터가 exit이면 반복 종료
            break;

        // 입력 받기
        printf("입력대기: ");
        fgets(input, sizeof(input), stdin); // 사용자 입력 받기
        input[strcspn(input, "\n")] = 0;    // 입력된 문자열에서 개행 문자 제거

        fd = open(FIFO_B_TO_A, O_WRONLY);   // 쓰기 전용으로 FIFO 파일 열기
        write(fd, input, strlen(input)+1);  // 입력받은 데이터를 FIFO를 통해 전송
        close(fd);                          // 파일 디스크립터 닫기

        if (strcmp(input, "exit") == 0)     // 입력받은 데이터가 exit이면 반복 종료
            break;
    }

    return 0;   // 프로그램 종료
}

실행 화면

△ Process A 실행 화면                   △ Process B 실행 화면

코드 설명

Named PIPE 방법 기반으로 구현하였으며 두 개의 Window PowerShell을 통해 SSH 연결하여 두 프로세스를 구비했다. 먼저 Process A의 코드의 진행 과정을 먼저 보자면 첫 번째로 FIFO 파일 생성을 생성한다. mkfifo() 함수를 사용해 fifo_a_to_b와 fifo_b_to_a 두 개의 FIFO 파일을 생성한다. 이 파일들은 서로 다른 프로세스 간의 데이터 전송 통로 역할을 합니다. 두 번째는 데이터 전송을 한다. 사용자로부터 입력을 받은 후 fifo_a_to_b FIFO 파일을 통해 Process B로 데이터를 전송한다. open(), write(), 그리고 close() 함수들을 이용하여 데이터를 쓰고 파일 디스크립터를 관리한다. 세 번째는 데이터 수신을 한다. fifo_b_to_a FIFO 파일을 통해 Process B로부터 데이터를 수신한다. 여기에는 open(), close()와 더불어 read() 함수를 사용하는데 이 점이 데이터 전송과의 차별을 가진다. 종료 조건으로는 exit 문자열을 전송하거나 수신받으면, FIFO 파일을 unlink() 함수로 삭제하고 프로그램을 종료한다.
Process B는 Process A와 달리 FIFO 파일이 존재한다고 가정을 한다. 이는 Process A에게 첫 전송에 대한 우선권이 있기 때문이다. 대신 이미 생성된 FIFO 파일을 열고 사용한다. 이 프로세스는 FIFO 파일 생성을 담당하지 않는다. 다음은 데이터 전송을 먼저하는 Process A와 달리 데이터를 먼저 수신한다. fifo_a_to_b FIFO 파일을 통해 Process A로부터 데이터를 먼저 수신한다. 다음에서야 데이터를 전송한다. 사용자로부터 입력을 받은 후 fifo_b_to_a FIFO 파일을 통해 Process A로 데이터를 전송한다. Process A와 같이 exit 문자열을 전송하거나 수신받으면 프로그램을 종료한다.
이 두 프로그램은 각각 다른 터미널이나 콘솔에서 실행한다. 본 목표는 SSH를 통해 Linux를 연결하였으며, 두 개의 Window PowerShell을 통해 진행했다. 두 프로그램은 FIFO 파일을 통해 서로 동기화되며, 한 프로그램이 데이터를 보내면 다른 프로그램이 이를 읽는다. 그렇기에 두 프로그램이 동시에 실행되고 있을 때 정상적으로 기능한다. 각 프로세스는 데이터를 전송하거나 수신할 준비가 되면 다음 단계로 넘어간다. 그리고 FIFO는 내부적으로 데이터가 쓰여잇는 순서대로 데이터를 전달한다.

느낀 점

본 목표를 진행하며, 아쉽다는 생각밖에 들지 않았던 것 같다. 두 프로세스간 IPC 기반 실시간 채팅 프로그램을 꼭 만들고 싶었으나 어려움을 겪었다. Message Queue를 이용하여 동시에 데이터를 접근하게 하고 이를 통해 해야겠다는 계획은 있었으나, 구현 단계에서 막혀 못하게 되었다. 두 가지 문제에 막히게 되었는데, 첫째는 exit를 작성하면 두 프로세스가 아닌 한 프로세스만 종료가 되는 문제였다. 둘째는 다섯번 반복해서 메시지를 입력하여야 다른 프로세스로 전송되는 문제였다. 그래서 아쉬움을 뒤로 하고 방향을 틀어 Named PIPE 개념을 이용하여 턴 방식의 채팅 프로그램이라도 구현했다. 이번 과제를 통해 많이 성장함을 느꼈지만 약간은 아쉽고 부족하다고 생각한다. 추가적인 시간을 할당하여 결국에는 해결하지 못한 저 두 문제점을 어떻게든 해결하여 꼭 실시간 채팅 프로그램을 만들어보아야겠다는 다짐을 하게 되었다. 뿐만 아니라, 이번 목표는 내가 IPC 그 중에서도, Named PIPE 개념을 이해하는데 큰 도움을 주었다. 직접 해봄으로써, 어떻게 동작하는지 더욱 직관적으로 확인할 수 있게 되었고, 이는 나에게 오랜 기억 속에 남는 개념 중 하나로 자리 잡았다고 생각한다. 앞으로의 운영체제 공부 중에서도 새롭게 나오는 개념이 있다면 직접 경험해봄으로써 견고하게 쌓아가야겠다는 생각이 들었다.

profile
코딩의 지식으로 하루를 마무리

0개의 댓글

관련 채용 정보