当前位置: 首页 > article >正文

【Hello Linux】进程间通信

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:介绍Linux进程间通信

进程间通信

  • 进程间通信
    • 概念
    • 进程间通信的目的
    • 进程通信的本质
    • 进程通信的分类
  • 管道
    • 什么是管道
    • 匿名管道
      • 匿名管道是什么
      • 匿名管道的原理
      • 创建匿名管道
      • 匿名管道的使用步骤
      • 匿名管道的特点
      • 管道的四种特殊情况
      • 匿名管道的大小
    • 命名管道
      • 命名管道是什么
      • 命名管道的原理
      • 系统中创建命名管道
      • 程序中创建命名管道
      • 命名管道实现通信
      • 命名管道实现进程遥控
      • 命名管道和匿名管道的区别
      • 命令行中的管道

进程间通信

概念

进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。

进程间通信的目的

我们都知道 进程是由程序员创建的 所以说进程之间通信本质就是程序员之间的通信

程序员在合作完成一个项目的时候需要同步数据

有时候在完成一个小demo之后需要共享这个demo的资源加快后序的开发进度

在一个小组的模块做完之后需要通知另外的一个小组进行后序的测试工作

如果测试的小组测出了bug要停止当前的开发修改此bug

所以说进程通信的目的有下面四个

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

进程通信的本质

进程间通信的本质就是让不同的进程看到同一份资源

我们都知道进程之间是具有独立性的 就拿父子进程对于同一个全局变量来说 如果子进程修改它的话会发生写时拷贝 从而达到一个保持进程独立性的效果

所以说我们如果想要两个进程看到同一份数据 这个数据肯定不能是属于某一个进程的

这个数据一定要属于操作系统 让操作系统来居中调度

在这里插入图片描述

由于这块资源可以由操作系统的不同模块来分配(内存 文件内核缓冲等)所以说这里就出现了很多种的进程通信方式

进程通信的分类

管道

  • 匿名管道
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

什么是管道

在Linux中,管道是一种进程间通信的方式,它把一个程序的输出直接连接到另一个程序的输入

我们使用ls指令能够查看目录下的文件

在这里插入图片描述

我们使用grep指令可以搜索关键字

那么如果我们想要查看目录下所有带有14这个关键字的文件呢?是不是可以输入下面的指令

  ls | grep 14

在这里插入图片描述

与此同时 结合我们之前的进程部分的学习 我们知道使用指令本质上也是在创建一个进程

所以说这里我们是不是就是在使用两个进程互相合作 既然两个进程在合作那么是不是它们之间一定发生了通信?

而这里实际上就是使用管道来进行进程间的通信

在这里插入图片描述
在这里插入图片描述

匿名管道

匿名管道是什么

匿名管道是一种用于父子进程之间通信的方式 它不占用外部存储空间 只存在于内存中

匿名管道的原理

我们在前面的讲解中说过 进程间通信的本质就是让不同的进程看到同一份资源 当然匿名管道也不例外

它的原理是让父子进程看到同一份文件资源 之后父子进程就可以对该文件进行写入或者是读取操作 从而实现进程间通信

在这里插入图片描述

这里有两个注意点

  • 父子进程看到的同一份文件是由操作系统来进行管理的 所以说父子进程写入数据的时候并不会发生写时拷贝
  • 虽然我们这里使用的是文件作为第三方资源 可是这里操作系统并不会将父子进程写入的数据刷新到物理内存中 因为这样子会牺牲效率 所以说我们这里所说的这种文件是内存中的文件

创建匿名管道

系统中给我们提供了一个函数来创建匿名管道 函数原型如下

  int pipe(int pipefd[2]);

它的返回值是一个整型 如果我们调用成功返回0 失败返回-1

它的参数是一个输出型参数 返回的是管道读端和写端的文件描述符(匿名管道只能单向读写 即只能通过一端写入 一端输出)

数组元素含义
pipefd[0]管道读端的文件描述符
pipefd[1]管道写端的文件描述符

我们可以这样子来形象的记忆

0代表嘴巴 用嘴巴来吃饭 所以应该是读端

1代表铅笔 用铅笔来写字 所以应该是写端

匿名管道的使用步骤

我们在前面的原理部分说过 匿名管道的管理其实就是让父子进程看到同一份资源

这里的先决条件是父子进程

所以说我们在使用匿名管道的时候一定会使用到fork函数和pipe函数

其具体步骤如下

  1. 父进程调用pipe函数创建管道

在这里插入图片描述

  1. 父进程创建子进程

在这里插入图片描述

  1. 父进程关闭写端 子进程关闭读端

在这里插入图片描述

这里有两点需要注意:

  1. 管道只能进行单向通信 这是因为如果可以双向通信有可能会造成自己写入的数据自己读取这种情况
  2. 从管道写端写入的数据会被内核缓冲 之后才会被读端读取

如果站在文件的角度我们可以这么理解

  1. 父进程调用pipe函数创建管道

在这里插入图片描述

  1. 父进程创建子进程

在这里插入图片描述

  1. 父进程关闭写端 子进程关闭读端

在这里插入图片描述

下面是代码示例

  1 // 这是一个测试管道的c语言程序
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <unistd.h>
  5 #include <string.h>
  6 #include <sys/types.h>
  7 #include <sys/wait.h>
  8 
  9 
 10 
 11 int main()
 12 {
 13   // 1. 父进程创建管道
 14   int fd[2] = {0};
 15 
 16   if (pipe(fd) == -1)
 17   {
 18     // -1表示创建失败
 19     perror("pipe error!\n");
 20     exit(-1);
 21   }
 22                                                                                                                            
 23   // 2. 父进程创建子进程
 24   pid_t pid = fork();
 25   if (pid == 0)
 26   {
 27     // child
 28     // 3. child write
 29     close(fd[0]); // 关闭读端
 30 
 31     // 4. send message
 32     const char* msg = "hello! im child!\n";
 33     int count = 5;                                                                                                         
 34     while(count--)
 35     {
 36       write(fd[1] , msg , strlen(msg));
 37       // 这里我们不需要把/0 拷贝到文件中因为这只是c语言字符串的规则
 38       sleep(2);
 39     }
 40     close(fd[1]);
 41     exit(0);
 42   }
 43   
 44 
 45   close(fd[1]);
 46   // father
 47   // 3. father read 
 48   // 关闭写端
 49   // 4. read message
 50   char buff[64] = {0}; 
 51   while (1)
 52   {
 53     ssize_t s = read(fd[0] , buff , sizeof(buff));
 54     if (s == 0)
 55     {
 56       // 写文件关闭了
 57       printf("write file close\n");
 58       break;
 59     }
 60     else if (s > 0)
 61     {
 62       printf("child say: %s",buff);
 63     }
 64     else
 65     {
 66       printf("error!\n");
 67       break;
 68     }
 69   }
 70   close(fd[0]);
 71   waitpid(-1 , NULL , 0);
 72   printf("wait child process success!\n");
 73   return 0;
 74 }

在这个程序中 我们使用父进程打开了一个管道并且创建了一个子进程

父进程关闭写端即只读 子进程关闭读端即只写

接着子进程向文件中写入五段消息 父进程读取这五段消息

子进程退出后 父进程回收子进程资源 结束

他的演示效果如下

请添加图片描述

匿名管道的特点

  1. 管道内部自带同步与互斥机制

在了解这个特点之前我们需要了解下面的几个概念

  • 临界资源: 临界资源是指一次仅允许一个进程使用的共享资源
  • 同步: 同步(Synchronization)是指在多线程或多进程环境中 确保多个线程或进程之间能够按照预定的顺序执行
  • 互斥: 互斥(Mutual Exclusion)是指在多线程或多进程环境中 确保对共享资源的访问是排他的 也就是说 在任何时刻 只有一个线程或进程能够访问共享资源

从临界资源的概念上来讲 我们很容易的推断出临界资源是需要被保护的

不然我们没法保证在同一时刻只有同一进程访问这一共享资源

为了形成对于临界资源的保护 操作系统会对管道进行同步和互斥

同步其实是一种更加复杂的互斥 而互斥是一种特殊的同步

  1. 管道的生命周期

管道本质上是通过文件进行通信的 也就是说管道依赖于文件系统

那么当所有打开该文件的进程都退出后 该文件也就会被释放掉 所以说管道的生命周期随进程

  1. 管道提供的是流式服务

我们首先要理解下面的两个概念

流式服务: 数据没有明确的分割 不分一定的报文段

数据报服务: 数据有明确的分割 拿数据按报文段拿

也就是说 对于子进程输入的数据父进程读取多少是任意的 这就是流式服务

  1. 管道是半双工通信的

在数据的通信中 数据的传输方式大致可以分为下面的三种

单工通信 指数据只能在一个方向上传输。例如,广播电台只能向外发送信号,而不能接收来自外部的信号。

半双工通信 允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输。它实际上是一种切换方向的单工通信。例如,对讲机就是一种半双工通信设备。

全双工通信 允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。例如,在打电话时,我们可以同时听到对方说话并且说话。

我们的管道就是一种典型的半双工通信

如果我们想要父子进程之间可以相互交流通信我们可以再创建一根管道

在这里插入图片描述

管道的四种特殊情况

我们在使用管道的时候会遇到下面四种特殊情况

  1. 写端不写 读端一直读

遇到这种情况的时候 读端会挂起 直到管道里面有数据时 读端才会被唤醒

代码表示如下 (其实我们前面写的演示代码就是这种情况 我们这里直接复用)

  1 // 这是一个测试管道的c语言程序
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <unistd.h>
  5 #include <string.h>
  6 #include <sys/types.h>
  7 #include <sys/wait.h>
  8 
  9 
 10 
 11 int main()
 12 {
 13   // 1. 父进程创建管道
 14   int fd[2] = {0};
 15 
 16   if (pipe(fd) == -1)
 17   {
 18     // -1表示创建失败
 19     perror("pipe error!\n");
 20     exit(-1);
 21   }
 22                                                                                                                            
 23   // 2. 父进程创建子进程
 24   pid_t pid = fork();
 25   if (pid == 0)
 26   {
 27     // child
 28     // 3. child write
 29     close(fd[0]); // 关闭读端
 30 
 31     // 4. send message
 32     const char* msg = "hello! im child!\n";
 33     int count = 5;                                                                                                         
 34     while(count--)
 35     {
 36       write(fd[1] , msg , strlen(msg));
 37       // 这里我们不需要把/0 拷贝到文件中因为这只是c语言字符串的规则
 38       sleep(2);
 39     }
 40     close(fd[1]);
 41     exit(0);
 42   }
 43   
 44 
 45   close(fd[1]);
 46   // father
 47   // 3. father read 
 48   // 关闭写端
 49   // 4. read message
 50   char buff[64] = {0}; 
 51   while (1)
 52   {
 53     ssize_t s = read(fd[0] , buff , sizeof(buff));
 54     if (s == 0)
 55     {
 56       // 写文件关闭了
 57       printf("write file close\n");
 58       break;
 59     }
 60     else if (s > 0)
 61     {
 62       printf("child say: %s",buff);
 63     }
 64     else
 65     {
 66       printf("error!\n");
 67       break;
 68     }
 69   }
 70   close(fd[0]);
 71   waitpid(-1 , NULL , 0);
 72   printf("wait child process success!\n");
 73   return 0;
 74 }

在这里插入图片描述

我们可以看到 子进程其实是隔两秒才开始写数据的 而父进程一直在读数据

要是按照我们的理解 其实第二次父进程读数据的时候就会跳出while循环了

可是并没有 这就是管道的第一种特殊情况造成的影响 如果写端不写 读端会挂起

  1. 读端不读 写端一直写

遇到这种情况的时候 当管道里面的数据写满后 写端会被挂起 当读取了一定的数据后 写端才会被唤醒

代码表示如下

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <unistd.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 
  8 int main()
  9 {
 10   // 1. ´ò¿ª¹ÜµÀ
 11   int fd[2] = {0};
 12   if (pipe(fd) == -1)
 13   {
 14     perror("pipe error!\n");
 15     exit(-1);
 16   }
 17 
 18   // 2. ´´½¨×Ó½ø³Ì
 19   pid_t pid = fork();
 20   if (pid == 0)
 21   {
 22     // child 1                                                                                                             
 23     close(fd[0]);
 24     int count = 0;
 25     while(1)
 26     {
 27       write(fd[1] , "a" , 1);
 28       count++;
 29       printf("child say success! :%d\n",count);
 30     }
 31   }
 32   // father
 33   close(fd[1]);
 34   // 4. read message
 35   waitpid(-1 , NULL , 0);
 36   return 0;
 37 }

我们可以看到 子进程一直在写数据 在写满了管道之后就挂起了

此时需要父进程读取一定的数据子进程才能够继续写

在这里插入图片描述

  1. 写端关闭

遇到这种情况的时候 读端会在读取完全部的数据之后继续执行下面的流程

代码表示如下

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <string.h>
  4 #include <stdlib.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 
  8 
  9 int main()
 10 {
 11   int fd[2] = {0};
 12   if (pipe(fd) == -1)
 13   {
 14     perror("pipe error!\n");
 15     exit(-1);
 16   }
 17 
 18   pid_t id = fork();
 19 
 20   if (id == 0)
 21   {
 22     // child                                                                                                               
 23     close(fd[0]);
 24     int count = 5;
 25     const char* msg = "hello world\n";
 26     while(count--)
 27     {
 28       write(fd[1] , msg , strlen(msg));
 29       sleep(1);
 30     }
 31     close(fd[1]);
 32     exit(0);
 33   }                                                                                                                        
 34 
 35   char buff[64];
 36   close(fd[1]);
 37   // father
 38   while(1)
 39   {
 40     ssize_t s =  read(fd[0] ,buff ,sizeof(buff));
 41     if (s == 0)
 42     {
 43       printf("write close\n");
 44       break;
 45     }
 46     printf("child say :%s",buff);
 47   }
 48 
 49   while(1)
 50   {
 51     printf("father still exist\n");
 52     sleep(1);
 53   }
 54   return 0;
 55 }                                                                       

我们可以看到 子进程停止写数据退出后 父进程在读完数据之后执行其他内容去了

在这里插入图片描述

读端关闭

遇到这种情况的时候 写端进程会直接退出

代码表示如下

  1 #include <stdio.h>                                                                                                         
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 
  8 
  9 
 10 int main()
 11 {
 12   int fd[2] = {0};
 13   if (pipe(fd) < 0) // -1 error
 14   {
 15     perror("pipe error!\n");
 16     return -1;
 17   }
 18 
 19   pid_t pid = fork();
 20   if (pid == 0) // child
 21   {
 22     close(fd[0]);
 23     const char* msg = "hello world!\n";
 24     while (1)
 25     {
 26       write(fd[1] ,msg , strlen(msg));
 27       printf("send success!\n");
 28       sleep(1);
 29     }
 30 
 31     // we do not set _exit func there
 32   }
 33 
 34   // father 
 35   close(fd[1]);
 36   sleep(6);
 37   close(fd[0]);
 38   waitpid(pid , NULL ,0);
 39   printf("wait child process success!\n");
 40   return 0;
 41 }

我们在这段代码中 在创建管道六秒后 将读端全部关闭 父进程等待子进程

但是我们并没有对子进程做任何事

我们来看看效果

在这里插入图片描述

我们可以发现 在六秒后读端关闭的瞬间 子进程退出了

这是为什么呢? 我们这里可以用到之前进程控制部分的相关知识 获取进程的退出信号 (因为这里的进程肯定不是正常退出的 所以查看退出码没有意义)

我们在原先的代码最后加上这段代码

  int status = 0;    
  waitpid(pid , &status ,0);    
  printf("wait child process success!\n");    
  printf("the singal is : %d\n", status & 0x7f);         

在这里插入图片描述

我们可以发现 这里的进程退出信号是13

接着我们使用kill -l指令查看所有的退出信号

在这里插入图片描述
那么现在我们就能知道了 当匿名管道发生情况四的时候 操作系统会向进程发送13号命令来终止进程

匿名管道的大小

我们可以复用上面的代码来测试出管道的大小

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <unistd.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>
  7 
  8 int main()
  9 {
 10   // 1. 创建管道
 11   int fd[2] = {0};
 12   if (pipe(fd) == -1)
 13   {
 14     perror("pipe error!\n");
 15     exit(-1);
 16   }
 17 
 18   // 2. 父子进程
 19   pid_t pid = fork();
 20   if (pid == 0)
 21   {
 22     // child 1                                                                                                             
 23     close(fd[0]);
 24     int count = 0;
 25     while(1)
 26     {
 27       write(fd[1] , "a" , 1);
 28       count++;
 29       printf("child say success! :%d\n",count);
 30     }
 31   }
 32   // father
 33   close(fd[1]);
 34   // 4. read message
 35   waitpid(-1 , NULL , 0);
 36   return 0;
 37 }

在这里插入图片描述

我们可以看到 子进程一共向管道中写入了65536字节的数据 也就是512kb

命名管道

命名管道是什么

命名管道是一种特殊的管道,存在于文件系统中。它也被称为先进先出队列 (FIFO) 。它可以用于任何两个进程间的通信 而不限于同源的两个进程

我们可以狭义上的理解匿名管道就是没有名字的管道而命名管道就是有名字的管道

但是实际上它们还是有很多的不同点 比如说匿名管道必须要同源的两个进程才能通信而命名管道则不需要

命名管道的原理

命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

这里我们有两点需要注意:

  1. 普通文件是很难做到通信的 即便做到通信也无法解决一些安全问题
  2. 命名管道和匿名管道一样 都是内存文件 只不过命名管道在磁盘有一个简单的映像 但这个映像的大小永远为0 因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中

系统中创建命名管道

我们在系统中一般使用mkfifo命令来创建一个命名管道

使用效果如下

在这里插入图片描述
此时我们就可以使用该管道来实现进程间的通信了

我们使用一个进程不停的往该管道文件内写入数据再使用一个进程不停的读取数据 效果如下:

请添加图片描述

在左边 我们使用了这样的一行shell脚本

while true; do echo "hello fifo" ; sleep 1 ; done > fifo

它的意思是每隔一秒不停的循环输出hello fifo 并将输出的hello fifo重定向到fifo文件中

在右边我们不停的在从fifo管道中读取数据

我们可以发现这样一个神奇的现象:当我们退出右边的进程的时候 左边的bash进程也退出了

这是为什么呢?

我们前面讲管道的四种特殊情况下讲过这一点 当管道的读端关闭的时候 写端进程会被操作系统使用13信号杀掉

而我们的shell脚本就是由bash进程执行的 所以说当我们关闭读端的左边的bash进程就终止了

程序中创建命名管道

我们在程序中创建命名管道要使用mkfifo函数 它的函数原型如下

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

返回值

  • 如果管道创建成功则返回 0
  • 如果管道创建失败则返回 -1

参数

  1. const char *pathname

第一个参数是一个字符串

  • 如果我们以路径的方式给出 则命名管道会默认创建在pathname路径下
  • 如果我们以文件名的方式给出 则命名管道会默认创建在当前路径

这里关于当前路径的概念 如果还有不理解的同学可以参考我的这篇博客

基础IO

  1. mode_t mode

这里设置是管道文件的权限 我们一般使用八进制的数字设置

当然权限的设置还和umask有关

有关权限的概念我之前的一篇博客以及详细介绍了 这里就不再赘述

Linux中权限

下面是该函数的使用

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 
  5 
  6 int main()
  7 {
  8   if (mkfifo("myfifo" , 0666) < 0)
  9   {
 10     perror("mkfifo fail!\n");
 11     return -1;
 12   }                                                                                                 
 13   return 0;
 14 }

上面这段代码的意思是 在当前路径下创建一个叫做mkfifo的命名管道

执行代码后查看当前目录下的文件 我们发现管道文件真的被创建了

在这里插入图片描述

命名管道实现通信

我们可以创建两个程序分别代表客户端 (client) 和服务端(server)

服务器创建一个命名管道 之后客户端和服务端全部打开该命名管道

服务端读数据 客户端写数据

服务端代码

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/stat.h>
  5 #include <string.h>
  6 #include <fcntl.h>
  7 #include <stdlib.h>
  8 #define FILENAME "myfifo"
  9 
 10 // 服务端要求创建一个命名管道 
 11 // 并且接受客户端发来的消息
 12 int main()
 13 {
 14   // open the fifo
 15   if (mkfifo(FILENAME , 0666) < 0)
 16   {
 17     perror("mkfifo error!\n");
 18     return -1;
 19   }
 20 
 21   // open the file 
 22   int fd = open(FILENAME , O_RDONLY);
 23   if (fd < 0)
 24   {
 25     perror("open fail!\n");                                                                                                
 26     return 1;
 27   }
 28 
 29   char msg[128];
 30   // read msg from fifo                                                                                                    
 31   while (1)
 32   {
 33     msg[0] = 0;
 34     ssize_t s = read(fd , msg , sizeof(msg)-1); 
 35     if (s > 0)
 36     {
 37       msg[s] = 0;
 38       printf("client say: %s\n" , msg);
 39     }
 40     else if (s == 0)
 41     {
 42       printf("client end!\n");
 43       break;
 44     }
 45     else 
 46     {
 47       perror("read error!\n");
 48       exit(-1);
 49     }
 50   }
 51   close(fd);
 52   return 0;
 53 }

而对于客户端来说 服务端已经将命名管道创建好了

所以说客户端只需要往管道里面写入数据就好了

服务端代码

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/stat.h>
  5 #include <string.h>
  6 #include <fcntl.h>
  7 #include <stdlib.h>
  8 #define FILENAME "myfifo"
  9 
 10 
 11 
 12 int main()                                                                                                                                      
 13 {
 14   int fd = open(FILENAME , O_RDWR);
 15   if (fd < 0)
 16   {
 17     perror("open error\n");
 18     exit(-1);
 19   }
 20 
 21   char msg[128];
 22   while(1)
 23   {
 24     msg[0] = 0;
 25     printf("Please write :");
 26     fflush(stdout);
 27     // the screen is line fflush but there is no \n
 28     ssize_t s = read (0 , msg , sizeof(msg) - 1);
 29     if (s > 0)
 30     {
 31       msg[s-1] = 0; // because the end of msg is :xxxx\n\0
 32       write(fd , msg , strlen(msg));
 33     }
 34   }
 35   close(fd);
 36   return 0;
 37 }

接下来我们只需要将服务端和客户端都运行起来 就能够实现两个进程之间的通信了

在这里插入图片描述

服务端和客户端之间的退出关系

我们这里首先要明白客户端是写端

服务端是读端

当写端退出后 读端会继续执行后面的程序

所以说客户端退出后 服务端会继续执行后面的代码

而当读端退出后 写端会被操作系统杀死

所以说服务端退出后 客户端会被操作系统杀死

通信是在内存中进行的

我们可以尝试让客户端一直写数据 但是服务端一直不读数据

之后查看fifo文件的大小

在这里插入图片描述

我们可以发现fifo文件的大小还是0 这说明我们的命名管道通信还是在内存当中进行通信的

命名管道实现进程遥控

我们通过命名管道可以实现一个进程对于另一个进程遥控

当然这里要利用到子进程和进程替换的一些知识(因为如果使用父进程进行进程替换的话替换一次服务端就停止服务了)

我们这里只需要对于服务端的代码进行一些修改

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/stat.h>
  5 #include <sys/wait.h>
  6 #include <string.h>
  7 #include <fcntl.h>
  8 #include <stdlib.h>
  9 #define FILENAME "myfifo"
 10 
 11 // 服务端要求创建一个命名管道 
 12 // 并且接受客户端发来的消息
 13 int main()
 14 {
 15   // open the fifo
 16   if (mkfifo(FILENAME , 0666) < 0)
 17   {
 18     perror("mkfifo error!\n");
 19     return -1;
 20   }
 21 
 22   // open the file 
 23   int fd = open(FILENAME , O_RDONLY);
 24   if (fd < 0)
 25   {
 26     perror("open fail!\n");
 27     return 1;                                                       
 28   }
 29 
 30   char msg[128];
 31   // read msg from fifo 
 32   while (1)
 33   {
 34     msg[0] = 0; 
 35     ssize_t s = read(fd , msg , sizeof(msg)-1); 
 36     if (s > 0)                                                      
 37     {
 38       msg[s] = 0;
 39       printf("client say: %s\n" , msg);
 40       if (fork() == 0)
 41       {
 42         execlp(msg , msg , NULL);
 43         exit(1);
 44       }
 45       waitpid(-1 , NULL , 0);
 46     }
 47     else if (s == 0)
 48     {
 49       printf("client end!\n");
 50       break;
 51     }
 52     else 
 53     {
 54       perror("read error!\n");
 55       exit(-1);
 56     }
 57   }
 58   close(fd);
 59   return 0;
 60 }

下面是实机效果

在这里插入图片描述

命名管道和匿名管道的区别

  • 匿名管道只能用于有亲缘关系的进程之间的通信 而命名管道能用于任意进程之间通信
  • 匿名管道使用pipe函数创建并打开 而命名管道使用mkfifo函数创建 由open函数打开
  • 匿名管道不在磁盘中创建文件 命名管道会在磁盘中创建文件

虽然它们有这些不同点 但是它们工作的时候都是在内存中传输数据的

命令行中的管道

我们在命令行中也可以使用管道来通信

比如说我们下面的指令
在这里插入图片描述
这就是我们在使用命令行中的管道进行通信

那么命令行中的管道是匿名管道还是命名管道呢?

答案是匿名管道 因为实际上我们在使用这个管道的时候磁盘上并没有创建文件


http://www.kler.cn/a/1595.html

相关文章:

  • SpringBoot3动态切换数据源
  • 创建Java项目,并添加MyBatis包和驱动包
  • 《C++11》右值引用深度解析:性能优化的秘密武器
  • /src/utils/request.ts:axios 请求封装,适用于需要统一处理请求和响应的场景
  • 平面坐标转大地坐标(arcgisPro中进行)
  • 【问题】配置 Conda 与 Pip 源
  • 浅谈C库函数——memcpy、memmove、memcmp、memset函数
  • 【日志包】go语言如何设计日志包 - 基于zap封装适合自己的日志包
  • Servlet的详细使用
  • OpenCv + Qt5.12.2 文字识别
  • JVM学习.01 内存模型
  • C语言刷题(6)(猜名次)——“C”
  • linux进程和进程通信编程(1)
  • 软件测试 - 非技术常见面试题
  • 【C++学习】日积月累——SLT中stack使用详解(1)
  • 012+limou+C语言深入知识——(4)“结构体”与“枚举体”与“联合体”
  • 011+limou+C语言深入知识——(3)“字符串函数”和“字符分类函数”和“内存操作函数”以及“部分库函数的模拟实现”
  • 【DP动态规划】最长子序列
  • [数据分析与可视化] Python绘制数据地图1-GeoPandas入门指北
  • string类(下)
  • 如何优雅地让谷歌浏览器中的网页旋转90度?掌握这个技巧,让你的网页与众不同!
  • linux kernel 5.0 inline hook框架
  • Mysql常用命令
  • 尚融宝07-前端模块化
  • 2023年网络安全最应该看的书籍,弯道超车,拒绝看烂书
  • 【C++编译】gcc、gdb、make、cmake