进程间通信-pipe

当我们进行设计项目的时候,通常会开启多个进程。这些进程间需要数据的交互,进程间通信就是为了解决这个问题而存在的。该文章先介绍pipe(管道)是如何解决这个问题的。

进程间通信的意义

数据传输: 一个进程需要将它的数据发送给另一个进程

资源共享 :多个进程之间共享同样的资源。

通知事件: 一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

**进程控制:**有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

如何进行通信

通信的本质还是信息的交互,秉持着"linux的一切皆文件"的想法,我们可以知道。进程间通信的本质:是先让不同的进程,先看到同一份资源"内存"。(然后才有通信的条件)。这份内存是由操作系统提供,不由任何一份进程提供。

管道

什么是管道

管道是Unix中最古老的进程间通信的形式。

我们把从一个进程连接到另一个进程的一个数据流称为一个"管道"。

其实我们以前已经用过管道了,如图中的命令。中间那个"|"就是管道。他的功能就是把 "ls l"的结果输入grep test,让grep命令在他的结果里面去找和test有关的内容。

四种通信情况

1.写慢/读快 -..-读端就要阻塞(进程),等待写入。

2.写快,读慢 ---满了了的时候,写就要阻塞等待,等待读取。

3.写关,继续读- --read就会读到返回值为0.表示文件结尾

4.读关闭,写继续-..写端在写入,没有任何意义,OS不会做没有意义的事情,OS会杀掉写端进程

用代码实现简单的管道通信

光说不练假把式,我们用一个简单的代码来实现一下简单的匿名管道通信。首先,我们先设置好管道,我们之前也有聊过,有一个东西叫做文件描述符,他们的默认前三位(0,1,2)分别是标准输入,标准输出和标准错误。因此,当我们创建管道的时候,进程会自动分配3和4作为我们的fd。我们使用pipe创建一下管道。就可以进行观察。

接着,我们fork一下,得到一个子进程。在子进程里面关掉输入流,然后开始往fd1里面写内容,把我们的数据给先传到管道里。传了五句话以后,就结束掉这个子进程。

在父进程里面,我们同样先关掉数据输出流,然后定义一个buffer,用于暂存管道传过来的数据。然后把数据打印出来。这里有几个细节:首先,'\0'是c语言的定义,是c语言默认在字符串后面加的,但是人家文件系统可不认这个东西。因此,我们需要保证,我们自己给它加上。我们就保证有效字符串里面最后一个字符一定是\0就好。第二个坑就是,我最开始写string msg = "this is child msg\n";没有加上'\n',所以后面父进程打印字符串的时候,只打印出一个this is child msg。这是因为,我们在写入文件时,五条字符串连续写入了,但是我们读取的时候由于缓冲区没有刷新,就只读到一条,或者说打印出1条。因此,我们需要在写入时使用\n,用于缓冲区的刷新以及换行。

复制代码
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <cstring>
#include <cstdlib>
using namespace std;
int main()
{
    int fd[2] = {0};
    pipe(fd);
    cout<<"fd[0]:"<<fd[0]<<'\n'<<"fd[1]:"<<fd[1]<<endl;
    int pid = fork();
    if(pid == 0)
    {
        close(fd[0]);
        int n = 5;
        while(n--)
      {    
        string msg = "this is child msg\n";
        write(fd[1],msg.c_str(),strlen(msg.c_str()));
        
      }
      close(fd[1]);
        exit(0);
    }
    close(fd[1]);
    char buffer[1024];
    int len = read(fd[0],buffer,sizeof(buffer)-1);
    if(len)
    buffer[len] = '\0';
    cout<<buffer<<endl;
    close(fd[0]);
    waitpid(pid,NULL,0);
    return 0;
}