进程间通信之管道通信

两个程序之间传递数据的一种简单方法是使用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将等待该进程的结束。
#include <stdio.h>

#define SIZE 1024*100

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;
}

管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道尾部写入数据,另一个进程(读进程)从管道的头部读出数据。管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
无名管道由pipe( )函数创建:

int pipe(int filedis[2]);

当一个管道建立时,它会创建两个文件描述符:filedis[0]fi用于读管道,ledis[1]
用于写管道。
这里写图片描述
管道用于不同进程间通信。通常先创建一个管道,在通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道描述符。必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符。

1、单个进程中的管道

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

#define SIZE 1024*100

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;
}

2、父子进程通过管道进行通信

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>

#define SIZE 1024

// 子进程通过管道从父进程接收数据
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;
}

3、父子进程通过管道实现文件复制

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

#define SIZE 1024

// 子进程通过管道从父进程接收数据
void child_do(int *fd)
{
    // 将管道的写端关闭
    close (fd[1]);

    int fd_write = open ("2.mmap", O_WRONLY|O_CREAT, 0777);
    if (fd_write == -1)
    {
        perror ("open");
        return;
    }
    int ret;
    char buf [SIZE];

    // read 从管道读数据,如果管道没有数据可读,read 会阻塞
    // 如果 管道的写端 被关闭, read 返回 0
    while (ret = read (fd[0], buf, SIZE))
    {
        if (ret == -1)
        {
            perror ("read");
            break;
        }

        // 把从父进程接收的数据写入到新文件中
        write (fd_write, buf, ret);
    }

    printf ("文件复制完成\n");
    // 关闭读端
    close (fd[0]);
    close (fd_write);
}

// 父进程通过管道向子进程发送数据
void father_do(int *fd)
{
    // 将管道读端关闭
    close (fd[0]);

    int fd_read = open ("1.mmap", O_RDONLY);
    if (fd_read == -1)
    {
        perror ("open");
        return;
    }
    int ret;
    char buf[SIZE];
    while (ret = read (fd_read, buf, SIZE))
    {
        if (ret == -1)
        {
            perror ("read");
            break;
        }

        // 把读到的内容发送给子进程
        write (fd[1], buf, ret);
    }

    // 关闭写端
    close (fd[1]);
    close (fd_read);
}

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;
}

4、管道读端关闭,写端继续写数据

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>

#define SIZE 1024

// 子进程通过管道从父进程接收数据
void child_do(int *fd)
{
    close (fd[1]);
    close (fd[0]);
}

void father_do(int *fd)
{
    // 将管道读端关闭
    close (fd[0]);

    printf ("等待子进程关闭读端\n");
    sleep(2);

    // 所有读端都关闭了,写端继续往管道写入数据
    // 如果管道所有的读端都被关闭,继续写数据系统默认的操作是使程序退出
    write (fd[1], "hello", 5);


    printf ("11111111111111111111111111111111\n");
    // 关闭写端
    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)和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。

命名管道具有很好的使用灵活性,表现在:

1) 既可用于本地,又可用于网络。

2) 可以通过它的名称而被引用。

3) 支持多客户机连接。

4) 支持双向通信。

5) 支持异步重叠I/O操作。

1、创建命名管道

#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。

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

int main()
{
    int ret = mkfifo("/home/mkfifo", 0777);
    if (ret == -1)
    {
        perror ("mkfifo");
        return -1;
    }

    return 0;
}

2、命名管道的传输
当打开FIFO时,非阻塞标识(O_NONBLOCK)将对以后的读写产生影响:
1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如果试图读取空的FIFO,将导致进程阻塞。
2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回。errno是ENXIO。

写入:

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

#define SIZE 1024

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;
}

读取:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 1024

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;
}

管道作为进程间通信的4种方式之一,他并不会保存数据,区别与共享内存。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值