进程间通信:管道

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程间通信:管道相关的知识,希望对你有一定的参考价值。

一、管道

  当从一个进程连接数据流到另一个进程时,我们使用术语管道(pipe)。我们通常是把一个进程的输出通过管道连接到另一个进程的输入。对于shell命令来说,命令的连接是通过管道字符来完成的,如:cmd1 | cmd2

1、进程管道

  可能最简单的在两个程序之间传递数据的方法就是使用popen和pclose函数了,它们的原型如下:

#include<stdio.h>
FILE *popen(const char *command,const char *open_mode);
int pclose(FILE *stream_to_close);

  popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或通过它接收数据。command字符串是要运行的程序名和相应的参数。open_mode必须是“r"或者“w”。这意味着我们不能调用另一个程序并同时对它进行读写操作。 

2、pipe调用

  pipe函数的原型如下:

#include<unistd.h>
int pipe(int file_descriptor[2]);

  pipe函数的参数是一个由两个整数类型的文件描述符组成的数组的指针。该函数在数组中填上两个新的文件描述符后返回0,如果失败则返回-1并设置errno来表明失败的原因。两个返回的文件描述符以一种特殊的方式连接起来。写到file_descriptor[1]的所有数据都可以从file_descriptor[0]读回来。数据基于先进先出的原则进行处理,这意味着如果把字节1,2,3写到file_descriptor[1],从file_descriptor[0]读到的数据也会是1,2,3。(这里使用的是文件描述符而不是文件流,所以我们必须用底层的read和write调用来访问数据,而不是用文件流库函数fread和fwrite。

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

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    char* data;
    pid_t fork_result;

    if (pipe(file_pipes) == 0) {
        fork_result = fork();
        if (fork_result == (pid_t)-1) {
            fprintf(stderr, "Fork failure");
            exit(EXIT_FAILURE);
        }

        if (fork_result == (pid_t)0) {
            close(0);
            dup(file_pipes[0]);
            //close(file_pipes[0]);
            close(file_pipes[1]);
            read(file_pipes[0],data,8);
            printf("%s\n",data);
            //execlp("od", "od", "-c", (char *)0);
            exit(EXIT_FAILURE);
        }
        else {
            close(file_pipes[0]);
            data_processed = write(file_pipes[1], some_data,strlen(some_data));
            close(file_pipes[1]);
            printf("%d - wrote %d bytes\n", (int)getpid(), data_processed);
        }
    }
    exit(EXIT_SUCCESS);
}

运行后得到如下结果:

Wrote 3 bytes
Read 3 bytes: 123

接下来,我们将学习如何在子进程中运行一个与其父进程完全不同的另外一个程序,而不是仅仅运行一个相同程序。我们用exec调用来完成这一工作。这里的一个难点是,通过exec调用的进程需要知道应该访问哪个文件描述符。在前面的例子中,因为子进程本身有file_pipes数据的一份副本,所以这并不成为问题。但经过exec调用后,情况就不一样了,因为原先的进程已经被新的子进程替换了。为了解决这个问题,我们可以将文件描述符(它实际上只是一个数字)作为一个参数传递给用exec启动的程序。

  为了演示它是如何工作的,我们需要使用两个程序,第一个程序是数据生产者,它负责创建管道和启动子进程,而后者是数据消费者。

如下pipe3.c:

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

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    char buffer[BUFSIZ + 1];
    pid_t fork_result;

    memset(buffer, \0, sizeof(buffer));

    if (pipe(file_pipes) == 0) {
        fork_result = fork();
        if (fork_result == (pid_t)-1) {
            fprintf(stderr, "Fork failure");
            exit(EXIT_FAILURE);
        }

        if (fork_result == 0) {
            sprintf(buffer, "%d", file_pipes[0]);
            (void)execl("pipe4", "pipe4", buffer, (char *)0);
            exit(EXIT_FAILURE);
        }
        else {
            data_processed = write(file_pipes[1], some_data,
                                   strlen(some_data));
            printf("%d - wrote %d bytes\n", getpid(), data_processed);
        }
    }
    exit(EXIT_SUCCESS);
}

数据消费者程序pipe4.c负责读取数据,如下:

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

int main(int argc, char *argv[])
{
    int data_processed;
    char buffer[BUFSIZ + 1];
    int file_descriptor;

    memset(buffer, \0, sizeof(buffer));
    sscanf(argv[1], "%d", &file_descriptor);
    data_processed = read(file_descriptor, buffer, BUFSIZ);

    printf("%d - read %d bytes: %s\n", getpid(), data_processed, buffer);
    exit(EXIT_SUCCESS);
}

  pipe3在程序中调用pipe4,运行pipe3时,我们得到如下结果:

19174 - wrote 3 bytes
19175 - read 3 bytes: 123

  pipe3程序的开始部分和前面例子一样,用pipe调用创建一个管道,然后用fork调用创建一个新进程。接下来,它用sprintf把读取管道数据的文件描述符保存到一个缓冲区中,该缓冲区中的内容将构成pipe4程序的一个参数。我们通过execl调用来启动pipe4程序。

3、把管道用作标准输入和标准输出

  主要用到以下两个函数:

#include<unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one,int file_descriptor_two);

  dup调用的目的是打开一个新的文件描述符,这与open调用有点类似。不同之处是,dup调用创建的新文件描述符与作为它的参数的那个已有文件描述符指向同一个文件(或管道)。对于dup函数来说,新的文件描述符总是取最小的可用值。而对于dup2函数来说,它所创建的新文件描述符或者与参数file_descriptor_two相同,或者是第一个大于该参数的可用值。

  用close和dup函数对文件描述符进行处理,当我们关闭文件描述符0,然后调用dup函数,可得如下结果:

文件描述符 初始值 关闭文件描述符0后 dup调用后
0 标准输入 {已关闭} 管道文件描述符
1 标准输出 标准输出 标准输出
2 标准错误输出 标准错误输出 标准错误输出
3 管道文件描述符 管道文件描述符 管道文件描述符
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    pid_t fork_result;

    if (pipe(file_pipes) == 0) {
        fork_result = fork();
        if (fork_result == (pid_t)-1) {
            fprintf(stderr, "Fork failure");
            exit(EXIT_FAILURE);
        }

        if (fork_result == (pid_t)0) {
            close(0);
            dup(file_pipes[0]);
            close(file_pipes[0]);
            close(file_pipes[1]);

            execlp("od", "od", "-c", (char *)0);
            exit(EXIT_FAILURE);
        }
        else {
            close(file_pipes[0]);
            data_processed = write(file_pipes[1], some_data,
                                   strlen(some_data));
            close(file_pipes[1]);
            printf("%d - wrote %d bytes\n", (int)getpid(), data_processed);
        }
    }
    exit(EXIT_SUCCESS);
}

  运行后得到如下结果:

19316 - wrote 3 bytes
0000000   1   2   3
0000003

 

以上是关于进程间通信:管道的主要内容,如果未能解决你的问题,请参考以下文章

Windows进程间通信—命名管道

linux c之通过管道实现兄弟间进程通信:

概述Linux进程间通信方式

利用管道实现进程间通信

Linux系统编程--进程间通信 ---管道篇

Linux_Centos进程间通信_管道(匿名管道_命名管道)