본문 바로가기

프로그래밍(TA, AA)/C C++

[통신프로그래밍] 파이프

파이프는 두 프로세스 사이에서 한 방향으로 통신할 수 있도록 지원합니다. 쉘에서 | 기호는 파이프를 의미합니다. 쉘에서 파이프 기능은 한 명령의 표준 출력을 다음 명령에서 표준 입력을 받아 수행하는 것을 의미합니다. 예를 들어보겠습니다.


# grep pipe test.c | more


앞에 있는 명령인 grep pipe test.c의 표준 출력을 다음 명령인 more의 표준 입력으로 사용합니다. 위 예를 실행하면 test.c에서 "pipe"라는 문자열이 위치한 행을 찾아 화면 단위로 출력(more)합니다. 파이프는 이름없는 파이프(익명 파이프:anonymouse pipe)와 (이름 있는 파이프:named pipe)로 구분됩니다.



이름 없는 파이프 : pipe

특별한 수식어 없이 그냥 파이프라고 하면 일반적으로 이름 없는 파이프(익명 파이프)를 의미합니다. 이름 없는 파이프는 부모-자식 프로세스 간에 통신할 수 있게 해줍니다. 부모 프로세스에서 fork 함수를 통해 자식 프로세스를 생성하고, 부모 프로세스와 자식 프로세스 간에 통신하는 것입니다. 따라서 부모 프로세스 → 자식 프로세스 또는 자식 프로세스 → 부모 프로세스 중 한 방향을 선택해야 합니다. 파이프를 이용해 양방향 통신을 원할 경우 파이프를 두 개 생성해야 합니다.


 기능

 함수원형

 간단한 파이프 생성

 FILE *popen(const char *command, const char *mode);

 int pclose(FILE *stream);

 복잡한 파이프 생성

 int pipe(int fildes[2]);



이름 있는 파이프 : FIFO

부모-자식 프로세스 관계가 아닌 독립적인 프로세스들이 파이프를 이용하려면 파일처럼 이름이 있어야 합니다. 이렇게 이름있는 파이프는 특수 파일의 한 종류로, FIFO라고도 합니다. 모든 프로세스가 이름 있는 파이프를 이용해 통신할 수 있습니다. 이름 있는 파이프 관련 명령과 함수는 아래 표와 같습니다.


 기능

 명령과 함수원형

 FIFO 생성 명령

 mknod name p

 mkfifo [-m mode] path...

 FIFO 생성 함수

 int mknod(const char *path, mode_t mode, dev_t dev);

 int mkfifo(const char *path, mode_t mode);




01. 이름 없는 파이프

파이프는 두 프로세스 간에 통신을 할 수 있도록 인터페이스를 제공합니다. 아무 수식어 없이 그냥 파이프라고 하면 일반적으로 이름 없는 파이프(익명 파이프:anonymous pipe)를 의미합니다. 이름 없는 파이프는 부모-자식 프로세스 간에 통신을 할 수 있게 해줍니다. 부모 프로세스에서 fork 함수를 사용해 자식 프로세스를 생성하고, 부모 프로세스와 자식 프로세스 간에 통신하는 것입니다.


파이프는 기본적으로 단방향입니다. 따라서 부모 프로세스가 출력한 내용을 자식 프로세스에서 읽을 것인지, 자식 프로세스가 출력한 내용을 부모 프로세스에서 읽을 것인지 둘 중 한 방향을 선택해야 합니다. 이는 수도관에 물을 보낼 때 양쪽에서 서로 보낼 수 없는 것과 같은 이치입니다. 이 절에서는 파이프 관련 함수인 popen, pclose, pipe 함수를 살펴보겠습니다.



간단한 파이프 생성


파이프를 만드는 가장 간단한 방법은 popen 함수를 사용하는 것입니다. 사용을 마친 파이프는 pclose 함수를 사용해 닫습니다.


파이프 생성: pipen(3)

#include <stdio.h>

FILE *popen(const char *command, const char *mode);

popen 함수는 다른 프로세스와 통신하기 위해 파이프를 생성합니다. 첫번째 인자인 command에는 쉘 명령을, 두번째 인자인 mode에는 "r"이나 "w"를 지정합니다. "r"은 파이프를 읽기 전용으로, "w"는 쓰기 전용으로 엽니다. popen 함수는 내부적으로 fork 함수를 실행해 자식 프로세스를 만들고 command에서 지정한 명령을 exec 함수로 실행해 자식 프로세스가 수행하도록 합니다. 자식 프로세스가 실행하는 exec 함수는 다음과 같은 형태가 됩니다.


excl("/usr/bin/sh", "sh", "-c", command, (char *)0);


popen 함수는 자식 프로세스와 파이프를 만들고 mode의 값에 따라 표준 입출력을 연결합니다. 리턴값은 파일 포인터입니다. 파일 입출력 함수에서 이 파일 포인터를 사용하면 파이프를 읽거나 쓸 수 있습니다. popen 함수는 파이프 생성에 실패하면 널 포인터를 리턴합니다.



파이프 닫기: pclose(3)

#include <stdio.h>

int pclose(FILE *stream);

pclose 함수는 파일 입출력 함수처럼 인자로 지정한 파이프를 닫습니다. pclose 함수는 관련된 waitpid 함수를 수행하며 자식 프로세스들이 종료하기를 기다렸다가 리턴합니다. pclose 함수의 리턴값은 자식 프로세스의 종료 상태입니다. 이 함수는 파이프를 닫는데 실패하면 -1을 리턴합니다.


popen 함수(쓰기 전용 모드)를 사용해 파이프를 생성해보겠습니다.

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

int main(void) {
    FILE *fp;
    int a;

    fp = popen("wc -l", "w");
    if(fp == NULL) {
        fprintf(stderr, "popen failed\n");
        exit(1);
    }

    for(a=0; a<100; a++)
        fprintf(fp, "test line\n");

    pclose(fp);

    return 0;
}
  • 08행: "w" 모드를 사용해 쓰기 전용 파이프를 생성하고 자식 프로세스는 wc -l 명령을 수행하도록 한다. wc -l은 입력되는 데이터의 행수를 출력하는 명령이다.
  • 14행~15행: 부모 프로세스에서는 반복문을 사용해 문자열을 파이프로 출력한다. 앞서 언급했듯이 자식 프로세스는 파이프로 입력되는 문자열을 읽어서 wc -l 명령을 수행한다. 결과를 보는 대신 이 예제의 실행 결과가 무엇일지 예측해보자.

이번에는 popen 함수(읽기 전용 모드)를 사용해 파이프를 생성해보겠습니다. 자식 프로세스가 파이프에 기록한 데이터를 부모 프로세스가 읽어서 처리하겠습니다.

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

int main(void) {
    FILE *fp;
    char buf[256];

    fp = popen("date", "r"); // 자식 프로세스에서는 date 명령을 수행.
    if(fp == NULL) {
        fprintf(stderr, "popen failed\n");
        exit(1);
    }
    // 부모 프로세스에서는 14행에서 자식 프로세스가 기록한 데이터를 읽고
    // 저장해  20행에서 출력한다.
    if(fgets(buf, sizeof(buf), fp) == NULL) {
        fprintf(stderr, "No data from pipe!\n");
        exit(1);
    }

    printf("line : %s\n", buf);
    pclose(fp);

    return 0;
}


복잡한 파이프 생성


popen 함수를 사용해 파이프를 생성하는 일은 간단하지만, 쉘을 실행해야 하므로 비효율적이고 주고받을 수 있는 데이터도 제한적입니다. popen 함수 대신에 pipe 함수를 사용하면 과정이 약간 복잡해지지만 파이프를 좀 더 효율적으로 생성할 수 있습니다.



파이프 만들기: pipe(2)

#include <unistd.h>

int pipe(int fildes[2]);

pipe 함수는 인자로 크기가 2인 정수형 배열을 받습니다. pipe 함수는 이 배열에 파일 기술자 2개를 저장합니다. fildes[0]은 읽기 전용으로 열고, fildes[1]은 쓰기 전용으로 엽니다. pipe 함수는 파이프를 생성하는 데 성공하면 0을, 실패하면 -1을 리턴합니다.



pipe 함수로 통신하는 과정

여기서는 pipe 함수로 생성한 파이프로 통신하는 과정을 살펴보겠습니다. 파이프를 생성하고나면 일반적으로 fork 함수를 호출해 자식 프로세스를 생성합니다. 자식 프로세스는 부모 프로세스가 pipe 함수로 생성한 파일 기술자들도 복사합니다. 이 파이프를 이용해 한 프로세스에서는 쓰기를 수행하고, 다른 프로세스에서는 읽기를 수행하면 통신이 되는 것입니다. 이 과정을 단계적으로 살펴보겠습니다.



ⓛ pipe 함수를 호출해 파이프에 사용할 파일 기술자를 얻는다. 파이프도 파일의 일종이므로 파일(파이프)을 읽고 쓸 수 있는 파일 기술자가 필요한데, 이를 pipe 함수가 생성해주는 것이다. 아래 그림과 같이 프로세스에 fd[0]과 fd[1]이 생성된다.





② fork 함수를 수행해 자식 프로세스를 생성한다. 이때 pipe 함수에서 생성한 파일 기술자도 자식 프로세스로 복사된다. 아래에서 보는 것처럼 같은 파일 기술자를 부모 프로세스와 자식 프로세스가 모두 가지고 있게 되는 것이다.




③ 파이프는 단방향 통신이므로 통신 방향을 결정한다. 예를 들어, 부모 프로세스는 쓰기를 하고 자식 프로세스는 읽기를 한다고 가정하자. 그러면 자식 프로세스는 읽기만 할 것이므로 쓰기용 파일 기술자인 fd[1]을 닫는다. 반대로 부모 프로세스는 쓰기만 할 것이므로 읽기용 파일 기술자인 fd[0]을 닫느다. 아래와 같이 부모 프로세스에서 자식 프로세스로 통신할 수 있도록 파일 기술자들이 정리되었다. 이제 부모 프로세스가 fd[1]에 쓴 내용을 자식 프로세스가 fd[0]에서 읽으면 된다.



만약 파이프의 쓰기 부분이 닫혀 있다면 파이프에서 읽으려고 할때 0이나 EOF가 리턴됩니다. 파이프의 읽기 부분이 닫혀 있다면 파이프에 쓰려고 할 때 SIGPIPE 시그널이 발생합니다.


파이프를 생성한 후 fork 함수로 자식 프로세스를 생성해 파이프로 부모-자식 프로세스를 연결하고, 부모 프로세스가 파이프에 출력한 문자열을 자식 프로세스가 파이프에서 읽어들이는 프로그램을 만들어보겠습니다.

#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int fd[2];
    pid_t pid;
    char buf[257];
    int len, status;

    if(pipe(fd) == -1) { // pipe 함수를 사용해 파이프를 생성한다. pipe 함수의 인자로는 파일 기술자를 저장할 배열을 지정한다.
        perror("pipe");
        exit(1);
    }

    switch(pid = fork()) { // fork 함수를 사용해 자식 프로세스를 생성한다.
        case -1:
            perror("fork");
            exit(1);
            break;
        // 자식 프로세스의 동작 부분이다. 24행에서 fd[1]을 닫는다. 이는 자식 프로세스에서
        // 파이프를 읽기용으로 사용하겠다는 의미다. 27행에서 파이프의 입력 부분인 fd[0]에서
        // 문자열을 읽어들인다.
        case 0: /* child */
            close(fd[1]);
            write(1, "Child Process:", 15);
            len = read(fd[0], buf, 256);
            write(1, buf, len);
            close(fd[0]);
            break;
        // 부모 프로세스의 동작 부분이다. 34행에서 fd[0]을 닫는다. 이는 파이프를
        // 출력용으로 사용하겠다는 의미다. 37행에서 fd[1]로 문자열을 출력한다.
        default:
            close(fd[0]);
            buf[0] = '\0';
            write(fd[1], "Test Message\n", 14);
            close(fd[1]);
            waitpid(pid, &status, 0);
            break;
    }

    return 0;
}


이번에는 쉘에서 많이 사용하는 명령 형태인 ps -ef | grep telnet의 동작을 구현해보도록 하겠습니다.

#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int fd[2];
    pid_t pid;

    if(pipe(fd) == -1) {  // 파이프를 생성한다.
        perror("pipe");
        exit(1);
    }

    // fork 함수를 사용해 자식 프로세스를 생성한다. pas 명령의 결과는
    // 기본으로 표준 출력으로 출력되고, grep 명령은 표준 입력을 통해
    // 입력받는다. 따라서 부모 프로세스와 자식 프로세스 간의 통신이
    // 표준 입출력 대신 파이프를 통해 이루어지도록 만들어야 한다.
    switch(pid = fork()) {
        case -1:
            perror("fork");
            exit(1);
            break;
        case 0: /* child */
            // 자식 프로세스가 해야 할 일은 부모 프로세스가 파이프로 출력하는
            // ps -ef 명령의 결과를 받아 grep telnet 명령을 수행하는 것이다.
            // 따라서 파이프의 출력 부분이 필요없으므로 fd[1]을 닫는다.
            close(fd[1]); // 읽기용으로 사용
            // fd[0]의 값이 0이 아니면, 즉 표준 입력이 아니면 fd[0]의 값을
            // 표준 입력으로 복사 한 후 fd[0]을 닫느다. 이제 자식 프로세스에서는
            // 표준 입력을 fd[0]이 가리키는 파이프에서 읽는다.
            if(fd[0] != 0) {
                dup2(fd[0], 0);
                close(fd[0]);
            }
            // 자식 프로세스가 grep 명령을 exec 함수로 호출한다. 이렇게 하면 grep 명령은
            // 표준 입력을 통해 데이터를 읽어들이려 한다. 이미 위쪽에 표준 입력으로 파이프의
            // 입력 파일 기술자를 복사했으므로 결과적으로 파이프를 통해 데이터를 읽어들인다.
            execlp("grep", "grep", "telnet", (char *)NULL);
            exit(1);
            break;
        default:
            // 부모 프로세스의 동작을 살펴보면 자식 프로세스와 크게 다를 것이 없음을 알 수 있다.
            // 우선 파이프의 입력 부분이 필요 없으므로 닫느다.
            close(fd[0]);
            // 파이프의 출력 부분을 표준 출력으로 복사한다. 따라서 부모 프로세스에서
            // 무언가를 출력하면, 파이프를 통해 출력된다.
            if(fd[1] != 1) {
                dup2(fd[1], 1);
                close(fd[1]);
            }
            // exec 함수를 사용해 ps -ef 명령을 실행한다. ps -ef의 명령은 기본으로 표준 출력으로
            // 출력하므로 결과가 파이프로 출력된다. 이 출력 결과를 자식 프로세스가 읽어 들인다.
            execlp("ps", "ps", "-ef", (char *)NULL);
            wait(NULL);
            break;
    }

    return 0;
}

[root@dev-web ch09]# ./ex9_4.out

root     16575 16574  0 11:07 pts/0    00:00:00 grep telnet




양방향 파이프의 활용


파이프는 기본적으로 단방향 통신을 수행한다고 했습니다. 따라서 파이프를 이용해 양방향 통신을 하려면 파이프를 두 개 생성하면 됩니다. 상수도와 하수도가 각각 별도의 파이프로 구성되어 있는 것과 같은 이치입니다. 예제를 통해 양방향 파이프를 활용하는 방법을 알아보겠습니다.


다음은 파이프 두개를 생성해 부모 프로세스와 자식 프로세스 간에 양방향 통신을 수행하는 예제입니다.

#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    int fd1[2], fd2[2];
    pid_t pid;
    char buf[257];
    int len, status;

    // 파이프를 하나 생성한다. 생성된 파일 기술자 fd1은 부모 프로세스에서
    // 자식 프로세스로 데이터를 보낼때 사용한다.
    if(pipe(fd1) == -1) {
        perror("pipe");
        exit(1);
    }

    // 또 다른 파이프를 생성한다. 파일 기술자 fd2는 자식 프로세스에서
    // 부모 프로세스로 데이터를 보낼때 사용한다.
    if(pipe(fd2) == -1) {
        perror("pipe");
        exit(1);
    }

    switch(pid = fork()) {
        case -1:
            perror("fork");
            exit(1);
            break;
        // 자식 프로세스는 부모 프로세스에서 오는 데이터를 읽는 데 사용할
        // fd1[0]을 남겨두고 fd1[1]을 닫느다. 또한 부모 프로세스로 데이터를 보내는데
        // 사용할 fd2[1]을 남겨두고 fd2[0]을 닫느다.
        case 0: /* child */
            close(fd1[1]);
            close(fd2[0]);
            write(1, "Child Process:", 15);
            len = read(fd1[0], buf, 256); // 부모프로세스로 부터 읽어온다.
            write(1, buf, len);
            strcpy(buf, "Good\n");
            write(fd2[1], buf, strlen(buf)); //자식 프로세스가 부모에게
            break;
        // 부모 프로세스는 자식 프로세스와 반대다. 자식 프로세스에 데이터를 보내는데 사용할 fd[1]을
        // 남겨두고, 자식 프로세스에서 오는 데이터를 읽는 데 사용할 fd2[0]을 남겨둔다.
        default:
            close(fd1[0]);
            close(fd2[1]);
            buf[0] = '\0';
            write(fd1[1], "Hello\n", 6); // 부모프로세스가 자식 프로세스에게
            sleep(2);
            write(1, "Parent Process:", 15);
            len = read(fd2[0], buf, 256); //자식프로세스로부터 읽어온다.
            write(1, buf, len);
            waitpid(pid, &status, 0);
            break;
    }

    return 0;
}

[root@dev-web ch09]# ./ex9_5.out

Child Process:Hello

Parent Process:Good


실행 결과를 보면 부모 프로세스와 자식 프로세스가 데이터를 주고받았음을 알 수 있습니다. 파일 기술자를 여닫느 과정이 다소 복잡하지만 파이프를 이용한 양방향 통신은 이와 같이 파이프를 두개 생성하면 가능합니다.



02. 이름있는 파이프

이름 없는 파이프는 부모-자식 프로세스 간에만 통신을 할 수 있다는 단점이 있습니다. 부모-자식 프로세스는 서로의 존재를 알고 있기 때문에 파이프에 별도의 이름을 붙여 구별하지 않아도 됩니다. 하지만 부모-자식 프로세스 관계가 아닌 독립적인 프로세스들은 서로의 존재를 알 수 없기 때문에 파이프를 이용하려면 파이프명이 있어야 합니다. 이런 기능을 제공하는 파이프를 이름 있는 파이프(named pipe)라고 합니다. 이름 있는 파이프는 특수 파일의 한 종류로 FIFO(First-In First-Out)라고도 합니다.


이름있는 파이프는 글자 그대로 이름이 붙은 파이프로 모든 프로세스가 이 파이프명을 이용해 통신할 수 있습니다. FIFO로 통신하려면 우선 FIFO 특수 파일을 생성하고 파일 입출력 함수를 사용하면 됩니다. 즉, 한 프로세스가 FIFO로 사용할 특수 파일을 생성하면, 이 파일의 이름을 알고 있는 다른 프로세스가 같은 FIFO를 이용해 통신을 수행할 수 있습니다.



명령으로 FIFO 파일 생성하기


명령으로 FIFO 파일을 생성하려면 mknod나 mkfifo 명령을 사용합니다.


FIFO, 특수 파일 생성: mknod 명령

mknod 명령은 FIFO 파일뿐만 아니라 특수 파일도 생성하는 명령입니다. mknod 명령으로 FIFO 파일을 생성할때는 다음과 같은 형식으로 사용합니다.


mknod 파일명 p


mknod 명령을 사용할 때는 FIFO 파일의 이름과 FIFO 파일을 생성하라는 의미인 p를 지정합니다. mknod 명령은 /usr/sbin 디렉토리에 위치하고 있습니다. /etc/mknod 파일은 /usr/sbin/mknod 파일에 대한 심볼릭 링크입니다. 


[root@dev-web ch09]# mknod HAN_FIFO p

[root@dev-web ch09]# ls -l HAN_FIFO

prw-r--r--. 1 root root 0 Sep 13 11:45 HAN_FIFO

[root@dev-web ch09]# ls -F HAN_FIFO

HAN_FIFO|


mknod 명령의 결과로 HAN_FIFO 파일이 생성되었습니다. ls -l 명령으로 확인해보면 HAN_FIFO 파일의 종류를 나타내는 문자가 p임을 알 수 있습니다. p는 FIFO 파일을 의미합니다. ls -F 명령으로 확인해보면 HAN_FIFO 파일의 뒤에 | 기호가 추가되었음을 알 수 있습니다. | 기호도 이 파일이 FIFO 파일임을 표시합니다.


FIFO 파일 생성: mkfifo 명령

mkfifo 명령은 FIFO 파일만 생성하는 명령입니다. mkfifo 명령으로 FIFO 파일을 생성할 때는 다음과 같은 형식으로 사용합니다.


/usr/bin/mkfifo [-m mode] path...


-m 옵션은 새로 생성되는 FIFO 파일의 접근 권한을 지정합니다. 이 옵션을 생략하면 unmask 값에 따라 기본 권한을 설정합니다.


예를 들어, mkfifo 명령으로 BIT_FIFO라는 FIFO 파일을 생성할 경우 다음과 같이 하면 됩니다. mknod 명령으로 생성한 파일과 같은 형태임을 알 수 있습니다.


[root@dev-web ch09]# mkfifo -m 0644 BIT_FIFO

[root@dev-web ch09]# ls -l BIT_FIFO

prw-r--r--. 1 root root 0 Sep 13 12:08 BIT_FIFO



함수로 FIFO 파일 생성하기


함수로 FIFO 파일을 생성하려면 mknod 또는 mkfifo 함수를 사용합니다. 명령과 함수의 이름이 같습니다.


특수 파일 생성: mknod(2)

#include <sys/stat.h>

int mknod(const char *path, mode_t mode, dev_t dev);

mknod 함수는 첫번째 인자인 path로 지정한 경로에 특수 파일을 생성합니다. 두번째 인자인 mode에는 특수 파일의 종류와 접근 권한을 지정합니다. mode에 지정하는 특수 파일의 종류는 다음 중 하나입니다.


  • S_FIFO: FIFO 특수 파일
  • S_IFCHR: 문자 장치 특수 파일
  • S_IFDIR: 디렉토리
  • S_IFBLK: 블록 장치 특수 파일
  • S_IFREG: 일반 파일


mode에 지정하는 접근 권한은 0777과 같이 숫자 모드를 직접 사용하는 것이 편합니다. 세번째 인자인 dev는 생성하려는 특수 파일이 블록 장치 특수 파일이나 문자 장치 특수 파일일 때만 의미가 있습니다. mknod 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.



FIFO 파일 생성: mkfifo(3)

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *path, mode_t mode);

mknod와  mkfifo 함수를 사용해 FIFO 파일을 생성해보겠습니다.

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    if(mknod("HAN-FIFO", S_IFIFO | 0644, 0) == -1) {
        perror("mknod");
        exit(1);
    }

    if(mkfifo("BIT-FIFO", 0644) == -1) {
        perror("mkfifo");
        exit(1);
    }

    return 0;
}

[root@dev-web ch09]# ls -l *FIFO

prw-r--r--. 1 root root 0 Sep 13 12:08 BIT_FIFO

prw-r--r--. 1 root root 0 Sep 13 12:29 BIT-FIFO

prw-r--r--. 1 root root 0 Sep 13 11:45 HAN_FIFO

prw-r--r--. 1 root root 0 Sep 13 12:29 HAN-FIFO



FIFO로 데이터 주고받기


명령이나 함수로 FIFO 파일을 생성하면 저수준 파일 입출력 함수로 이 파일을 읽거나 쓸수 있습니다. open 함수로 FIFO 파일을 열 때는 O_NONBLOCK 옵션의 영향을 받습니다. O_NONBLOCK 옵션이 설정되어 있지 않으면 다른 프로세스가 읽기 위해 열 때까지 쓰기 위한 open 함수는 블록됩니다. 반대의 경우도 마찬가지입니다. O_NONBLOCK 옵션이 설정되어 있으면 open 함수는 즉시 리턴합니다. FIFO에서 데이터를 읽으려는 프로세스가 없는데 쓰기 위한 open 함수를 호출하면 오류가 발생합니다.


FIFO도 파이프와 마찬가지로 단방향 통신입니다. 따라서 FIFO 파일을 읽거나 쓰기 위해 열 경우 반드시 읽기 전용(O_RDONLY)이나 쓰기 전용(O_WRONLY)으로만 열어야 합니다. 읽고 쓰기용(O_RDWR)으로 열수는 없습니다.


예제를 통해 FIFO 파일을 이용한 통신 방법을 살펴보겠습니다. 이번 예제는 서버 프로그램과 클라이언트 프로그램으로 구분됩니다. 서버 프로세스에서 보낸 문자열을 클라이언트 프로세스가 받아서 출력하는 예제입니다.

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    int pd, n;
    char msg[] = "Hello, FIFO";

    printf("Server =====\n");

    // mkfifo 함수로 HAN-FIFO라는 FIFO 파일을 생성한다.
    if(mkfifo("./HAN-FIFO", 0666) == -1) {
        perror("mkfifo");
        exit(1);
    }

    // 서버에서 클라이언트로 데이터를 전송할 것이므로
    // HAN-FIFO 파일을 쓰기 전용으로 연다.
    if((pd = open("./HAN-FIFO", O_WRONLY)) == -1) {
        perror("open");
        exit(1);
    }

    printf("To Client : %s\n", msg);

    // 클라이언트로 "Hello, FIFO"라는 메시지를 전송한다.
    n = write(pd, msg, strlen(msg)+1);
    if (n == -1) {
        perror("write");
        exit(1);
    }
    close(pd);

    return 0;
}


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

int main(void) {
    int pd, n;
    char inmsg[80];

    // HAN-FIFO 파일을 읽기 전용으로 연다. 클라이언트 프로그램은 이미 HAN-FIFO 파일이 있다고
    // 가정하고 파일을 열고 있으므로 서버 프로그램을 먼저 실행해야 한다.
    if ((pd = open("./HAN-FIFO", O_RDONLY)) == -1) {
        perror("open");
        exit(1);
    }

    printf("Client =====\n");
    write(1, "From Server : ", 13);

    // 반복문을 수행하며 FIFO 파일에서 데이터를 읽어와 출력한다.
    while((n=read(pd, inmsg, 80)) > 0)
        write(1, inmsg, n);

    if(n == -1) {
        perror("read");
        exit(1);
    }

    write(1, "\n", 1);
    close(pd);

    return 0;
}

[root@dev-web ch09]# ./ex9_7s.out

Server =====

To Client : Hello, FIFO


[root@dev-web ch09]# ./ex9_7c.out
Client =====
From Server :Hello, FIFO



요약


파이프

파이프는 두 프로세스 사이에서 한 방향으로 통신할 수 있도록 지원하는 특수 파일을 의미합니다.

파이프는 이름 없는 파이프(익명 파이프)와 이름 있는 파이프로 구분됩니다.


이름 없는 파이프

이름없는 파이프는 부모-자식 프로세스 간에 통신할 수 있도록 합니다.

파이프는 기본적으로 단방향으로 통신을 수행합니다.

파이프를 이용해 양방향 통신을 하려면 파이프 두 개를 만들어야 합니다.


이름 있는 파이프

부모-자식 프로세스 관계가 아닌 독립적인 프로세스들은 서로의 존재를 알 수 없기 때문에 파이프를 이용하려면 파이프명이 있어야 합니다. 이런 기능을 제공해 주는 파이프를 이름 있는 파이프라고 합니다.

이름 있는 파이프는 특수 파일의 한 종류로, FIFO(First-In First-Out)라고도 합니다.

FIFO는 명령으로 생성할 수도 있고, 프로그램 안에서 함수로 생성할 수도 있습니다.