【进程间通信】管道和命名管道

文章目录

  • 进程间通信的目的
  • 管道
    • 匿名管道
      • 管道的读写规则
      • 命名管道
        • 命名管道和匿名管道区别

          进程间通信的目的

          1. 数据传输:一个进程需要将它的数据发送给另一个进程
          2. 资源共享:多个进程之间共享同样的资源。
          3. 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
          4. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

          原理:因为进程进是相互独立的,所以通信的本质就是让两个进程看到同一份公共资源。

          管道

          匿名管道

          管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。平时在命令行中的 | 指令就是管道。

          所以在指令级别我们可以很轻松的使用管道,那么在编码的时候如何使用管道通信呢?

          我们来认识一个系统调用pipe。

          这种创建出来的管道没有名字,成为匿名管道。管道一般是单向通信的,也就是说一个人只能读或者写。我们可以看到fd数字是一个文件描述符数组,所以Linux管道是复用了很多文件部分的代码的,所以在通信的时候很多还是文件的操作。那么接下来说一下怎么通信:

          匿名管道主要是通过fork,子进程会复用大部分父进程的数据和代码的思想来让子进程和父进程看到同一份资源的。

          1. 父进程创建管道

          2. fork创建子进程

          3. 根据自己需求,各自关闭一个读端或者写端

          这种管道也是文件,但是它的数据不会往磁盘中刷新,因为没有必要.

          从上图中可以看到一个文件被打开了两次,那么它真的会被打开两次吗?

          一个文件被打开了两次,因为他们打开的方式不一样,所以他们的标记为也一定不一样,它们在内存中的file结构体一定是存在两份的,但是文件的属性和操作集合都是一样的,所以文件属性和操作集合以及文件缓冲区是存在一份的,每个file结构体中会有一些基本的属性(读写位置等等),其次还会有一些指针,分别指向自己的属性和方法集合以及缓冲区。

          管道的读写规则

          四种情况

          1. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止。
          2. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止。
          3. 写端关闭,读端一直读取,读端会读到read的返回值为0,表示读到文件的结尾。
          4. 读端关闭,OS会直接杀掉写端的进程,通过向目标进程发送SIGPIPE(13)信号,终止目标进程。

          五种特性

          5. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,仅限与此。

          6. 匿名管道,默认给读写端提供同步机制。

          7. 面向字节流。

          8. 管道的生命周期是随进程的。

          9. 管道是单向通信,半双工通信的一种特殊情况。

          #include #include #include #include #include #include #define MAX 1024
          using namespace std;
          int main()
          { // 第1步,建立管道
              int pipefd[2] = {0};
              int n = pipe(pipefd);
              assert(n == 0);
              (void)n; // 防止编译器告警,意料之中,用assert,意料之外,用if
              // 第2步,创建子进程
              pid_t id = fork();
              if (id < 0)
              { perror("fork");
                  return 1;
              }
              // 子写,父读
              // 第3步,父子关闭不需要的fd,形成单向通信的管道
              if (id == 0)
              { close(pipefd[0]);
                  int cnt = 0;
                  while(true)
                  { char message[MAX];
                      snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
                      cnt++;
                      write(pipefd[1], message, strlen(message));
                      sleep(1);
                      // if(cnt > 3) break;
                  }
                  exit(0);
              }
              // 父进程
              close(pipefd[1]);
              // r
              char buffer[MAX];
              while(true)
              { ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
                  if(n > 0)
                  { buffer[n] = 0; 
                      cout << getpid() << ", " << "child say: " << buffer << " to me!" << endl;
                  }
                  else if(n == 0)
                  { cout << "child quit, me too !" << endl;
                      break;
                  }
                  sleep(1);
                  break;
              }
              close(pipefd[0]);
              sleep(5);
              int status = 0;
              pid_t rid = waitpid(id, &status, 0);
              if (rid == id)
              { cout << "wait success, child exit sig: " << (status&0x7F) << endl;
              }
              return 0;
          }
          

          命名管道

          匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

          如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

          命名管道是一种特殊类型的文件。

          指令我们可以使用mkfifo来创建一个命名管道

          编码的话也可以使用mkfifo来创建一个命名管道文件。

          命名管道如何保证两个不同的进程间看到同一份公共资源呢?

          因为路径是唯一的,通过路径+文件名的方式可以确保两个不同的进程看到同一份公共资源,看到之后,他也是文件,它的原理就和匿名管道一样了,并且它的特性除了局限于亲缘通信之外,其他和匿名管道都差不多。

          命名管道和匿名管道区别

          1. 匿名管道由pipe函数创建并打开。
          2. 命名管道由mkfifo函数创建,打开用open
          3. FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完

            成之后,它们具有相同的语义