進程通信1——管道通信

管道是單向的、先進先出的,它把一個進程的輸出和另一個進程的輸入連接在一起。一個進程(寫進程)在管道尾部寫入數據,另一個進程(讀進程)從管道的頭部讀出數據。
兩個程序之間傳遞數據的一種簡單方法是使用popen和pclose。

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen函數允許一個程序將另一個程序作爲新進程來啓動,並可以傳遞數據給它或者通過它接收數據。command字符串是要運行的程序名和相應的參數。type必須是"r"或"w"。
如果type是"r",被調程序的輸出就可以被調用程序使用,調用程序利用popen函數返回的FILE *文件流指針,可以讀取被調程序的輸出;如果type是"w",調用程序就可以向被調程序發送數據,而被調程序可以在自己的標準輸入上讀取這些數據。
pclose函數只在popen啓動的進程結束後才返回。如果調用pclose時它仍在運行,pclose將等待該進程的結束。

案例:

int main()
{
    FILE *fp = popen("ps -ef", "r");
    if (fp == NULL)
    {
        perror ("popen");
        return -1;
    }

    char buf[SIZE] = {0};

    int ret = fread(buf, sizeof(char), SIZE-1, fp);

    // printf ("讀到的數據:\n %s\n", buf);


    FILE *fp2 = popen("grep a.out", "w");
    if (fp2 == NULL)
    {
        perror ("popen");
        return -1;
    }

    fwrite (buf, sizeof(char), ret, fp2);
    printf ("寫入完成\n");

    pclose (fp);
    pclose (fp2);

    return 0;
}

從案例可以看出通過popen這個函數可以實現與終端語句ps -ef | grep a.out相同的功能。

管道包括無名管道和有名管道兩種,前者用於父進程和子進程間的通信,後者可用於運行於同一系統中的任意兩個進程間的通信。

無名管道由pipe()函數創建:
int pipe(int filedis[2]);
當一個管道建立時,它會創建兩個文件描述符:
filedis[0]用於讀管道,filedis[1]用於寫管道。
案例:

int main()
{
    int fd[2];

    int ret = pipe(fd);
    if (ret == -1)
    {
        perror ("pipe");
        return -1;
    }

    ret = write (fd[1], "hello", 5);


    printf ("寫入 %d 個字節\n", ret);
    char ch;
    while (1)
    {
        // 如果管道里面沒有數據可讀,read會阻塞
        ret = read (fd[0], &ch, 1);
        if (ret == -1)
        {
            perror ("read");
            break;
        }

        printf ("讀到 %d 字節: %c\n", ret, ch);
    }
    close (fd[0]);
    close (fd[1]);

    return 0;
}

從案例可以看出通過pipe 這個函數,可以創建一個管道,fd[0]用於讀,f[1]用於寫。

管道用於不同進程間通信。通常先創建一個管道,在通過fork函數創建一個子進程,該子進程會繼承父進程所創建的管道描述符。
案例:

void child_do(int *fd)
{
    // 將管道的寫端關閉
    close (fd[1]);

    char buf [SIZE];

    while (1)
    {
        // 從父進程讀取數據
        int ret = read (fd[0], buf, SIZE-1);
        if (ret == -1)
        {
            perror ("read");
            break;
        }
        buf[ret] = '\0';
        printf ("子進程讀到 %d 字節數據: %s\n", ret, buf);
    }

    // 關閉讀端
    close (fd[0]);
}

// 父進程通過管道向子進程發送數據
void father_do(int *fd)
{
    // 將管道讀端關閉
    close (fd[0]);

    char buf[SIZE];

    while (1)
    {
        fgets (buf, SIZE, stdin);

        // 向子進程發送數據
        int ret = write (fd[1], buf, strlen(buf));
        printf ("父進程發送了 %d 字節數據\n", ret);
    }

    // 關閉寫端
    close (fd[1]);
}

int main()
{
    int fd[2];

    // 創建管道
    int ret = pipe(fd);
    if (ret == -1)
    {
        perror ("pipe");
        return -1;
    }

    // 創建子進程
    pid_t pid = fork();

    switch (pid)
    {
        case -1:
            perror ("fork");
            break;
        case 0:   // 子進程
            child_do(fd);
            break;
        default:
            father_do(fd);
            break;
    }

    return 0;
}

在這個案例中,父進程用來寫,子進程則用來讀,這樣父子進程,就可以通過管道進行通信了。

命名管道(FIFO)和無名管道基本相同,但也有不同點:無名管道只能由父子進程使用;但是通過命名管道,不相關的進程也能交換數據。

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

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

pathname: FIFO文件名
mode:屬性(同文件操作)

一旦創建了一個FIFO,就可用open打開它,一般的文件訪問函數(close、read、write等)都可用於FIFO。
命名管道的創建比較簡單,就不多說了,下面介紹一個用命名管道進行的信息交互的案例:

int main()
{
    int fd = open("/home/mkfifo", O_WRONLY);
    if (fd== -1)
    {
        perror ("mkfifo");
        return -1;
    }

    char buf[SIZE];
    while (1)
    {
        fgets (buf, SIZE, stdin);

        write (fd, buf, strlen(buf));
    }

    return 0;
}
int main()
{
    int fd = open("/home/mkfifo", O_RDWR);
    if (fd == -1)
    {
        perror ("mkfifo");
        return -1;
    }

    char buf[SIZE];
    while (1)
    {
        int ret = read (fd, buf, SIZE);
        buf[ret] = '\0';

        printf ("讀到 %d 字節: %s\n", ret, buf);
    }

    return 0;
}

在打開兩個命名管道後,一個進行讀操作,一個進行寫操作。

關閉管道只需要將兩個文件描述符關閉即可,可以使用普通的close函數逐個關閉。

發佈了53 篇原創文章 · 獲贊 11 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章