【Linux】命名管道命名管道和匿名管道的对比命令行中的管道
创始人
2024-06-02 20:25:27
0

文章目录

    • 命名管道
      • 用命名管道实现server&client通信
        • comment.h
        • server.c
        • client.c
        • 效果展示
    • 命名管道和匿名管道的对比
    • 命令行中的管道

命名管道

匿名管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间的通信

  • 匿名管道是通过子进程继承父进程的所打开的文件,从而获取到该文件的内核缓冲区作为通信资源

如果要实现两个毫不相关进程之间的通信, 所以引入了命名管道,其可以在两个不相关进程进行通信

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


在程序中创建命名管道使用mkfifo函数:

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

image-20220802200408959

参数解析:

第一个参数pathname:表示要创建的命名管道文件

  • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下
  • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下

第二个参数mode_t mode: 表示创建命名管道文件的默认权限

注意:如果我们想将mode设置为666:

image-20220803213026407

实际上,创建出来文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来文件的权限为:mode&(~umask) ,其中umask的默认值为0002,所以我们设置mode值为0666时实际创建出来文件的权限为0664


若想创建的命名管道文件的权限值不受umask的影响,则要在创建文件前使用 umask 函数将文件默认掩码设置为0

#include 
#include 
mode_t umask(mode_t mask);

umask也是系统调用,可以在程序创建文件的时候,指定程序上下文环境的umask,而不影响系统的umask

image-20220803213253583

返回值:

如果管道文件存在,再创建就会报错,这样可以保证管道文件为最新的, 命名管道创建成功返回0, 创建失败返回-1


小例子:利用命名管道进行命令行通信

这里写一个脚本往管道写入数据:

while :; do echo "hello Mango"; sleep 1; done > fifo
image-20220804103559028

左边的进程A用shell脚本每秒向命名管道写入一个字符串,右边的进程B,用cat命令从命名管道当中进行读取

现象就是当进程A启动后,进程B会每秒从命名管道中读取一个字符串打印到显示器上,也就是进程间通信

image-20220804103618094

当管道的读端进程退出后,写端进程再向管道写入数据就没有意义了,此时写端进程会被操作系统杀掉,在这里就可以很好的得到验证 :

当我们终止掉读端进程后,因为写端执行的循环脚本是由命令行解释器bash执行的,所以此时bash就会被操作系统杀掉,于是我们的云服务器也就退出了


用命名管道实现server&client通信

匿名管道是借助了子进程对父进程的继承性让两个进程看到同一份资源 . 命名管道是通过 路径/文件名的方式定位 唯一的磁盘文件

这里我们希望让server.c 和 client.c这两个进程进行相互通信, 所以我们先写一个Makefile方便我们后序编译


注意:这里我们需要让Makefile一次帮我们生成两个可执行程序,

Makefile自顶向下扫描,默认只会生成第一个目标文件, 所以如果要一次性生成两个可执行程序, 就需要定义一个伪目标:.PHONY:all,并添加对应的依赖关系

.PHONY:all	#定义一个伪目标,依赖的是client和server,  没有依赖方法
all:server clientserver:server.cgcc -o $@ $^
client:client.cgcc -o $@ $^.PHONY:clean
clean:rm -rf client server fifo	#注意这里要把我们创建的管道fifo也删了,不然再次运行的时候会创建失败!

解析:

这样Makefile先看到的是all这个伪目标,要找client 和 server,如果没有就会根据client 和 server的依赖关系和依赖方法分别形成client 和 server, 形成之后,因为all没有依赖方法,最终什么都不做,这样就形成了client和server两个可执行程序!s


我们只需要通信双方进行 文件操作通信即可,

comment.h

对于如何让客户端和服务端使用同一个命名管道文件,这里我们可以让客户端和服务端包含同一个头文件,该头文件当中提供这个共用的命名管道文件的文件名,这样客户端和服务端就可以通过这个文件名,打开同一个命名管道文件,进而进行通信了

用于存放通信双方共有的头文件, comment.h也是两个程序能够看到同一份资源

#pragma once
#include
#include
#include 
#include 
#include 
#include #define MY_FIFO "./fifo"  //管道的创建路径

server.c

要干的事情: 创建管道, 读取client发送的信息,并实现对应的业务逻辑

#include"comment.h"int main()
{umask(0);将文件默认掩码设置为0if(mkfifo(MY_FIFO,0666)<0)  //创建管道{perror("mkfifo:");return 1;}int fd = open(MY_FIFO,O_RDONLY);//以读方式打开命名管道文件if(fd<0){perror("open");return 2;}//处理业务逻辑,进行相应的读写while(1){char buffer[64] = {0};ssize_t s =  read(fd,buffer,sizeof(buffer)-1);//少读一个字节,防止读满了之后在末尾置\0越界if(s > 0){//读取成功buffer[s] = 0;//字符串末尾置\0printf("client send: %s\n",buffer);}else if(s == 0){//对端关闭了printf("client close\n");break;}else{//读取错误perror("read:");break;}}close(fd);return 0;
}

client.c

因为我们在server.c文件中已经创建了管道了,所以此时不需要再创建管道, 只需要获取已经打开的管道文件即可!

client.c需要干的事情: 从键盘中读取数据, 然后把数据发送 ->即:向管道中写入数据

#include"comment.h"
#include    //strlen函数
int main()
{//此时不需要创建管道,只需要获取即可int fd = open(MY_FIFO,O_WRONLY);//以写方式打开命名管道文件if(fd<0){perror("open:");return 1;}//处理业务逻辑while(1){char buffer[64] = {0};//1.先从键盘读取内容printf("enter Message: ");fflush(stdout);//刷新缓冲区ssize_t s = read(0,buffer,sizeof(buffer)-1);//从标准输入读取内容,少读一个字节,防止读满了!if(s > 0){buffer[s -1]= 0;//提前置\0,把\n覆盖掉printf("%s\n",buffer);//把我们从键盘获取到的数据打印出来看一下//向管道中写入数据write(fd,buffer,strlen(buffer));}}close(fd);return 0;
}

注意:因为系统接口函数write会把回车也认为是读取到的内容,所以我们在键盘中读取完之后, 在字符串末尾把\n给覆盖掉!


效果展示

注意:实现服务端(server)和客户端(client)之间的通信之前,我们需要先让服务端运行起来,让服务端运行后创建一个命名管道文件,然后再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了

image-20220803215949038

case


除此之外,我们还可以让client 控制 server来执行一些事情, 这也是进程通信的一个目的!

补充server.c的业务逻辑: 如果client发送的信息是"show-ls-l" :就执行ls -l命令 如果client发送的信息是"show-train" :就执行sl命令,否则正常打印client发送的内容

#include"comment.h"
#include  //strcmp函数
#include  //exit函数
#include    //waitpid函数
int main()
{umask(0);//清空权限掩码if(mkfifo(MY_FIFO,0666)<0)  //创建管道{perror("mkfifo:");return 1;}int fd = open(MY_FIFO,O_RDONLY);//以读方式打开if(fd<0){perror("open");return 2;}//处理业务逻辑,进行相应的读写while(1){char buffer[64] = {0};ssize_t s =  read(fd,buffer,sizeof(buffer)-1);//少读一个字节,防止读满了之后在末尾置\0越界if(s > 0){//读取成功buffer[s] = 0;//字符串末尾置\0if(strcmp(buffer,"show-ls-l") == 0){if(fork() == 0)//创建子进程帮我们干活{execl("/usr/bin/ls","ls","-l",NULL);//进程替换exit(1);//进程替换成功之后是不会走到这里的}waitpid(-1,NULL,0);}else if(strcmp(buffer,"show-train") == 0 )   //小火车{if(fork() == 0){execl("/usr/bin/sl","sl",NULL);exit(1);}waitpid(-1,NULL,0);}else{printf("client send:%s\n",buffer);}}else if(s == 0){//对端关闭了printf("client close\n");break;}else{//读取错误perror("read:");break;}}close(fd);return 0;
}

如果我们让server.c不读取,休眠100s,验证管道的数据会不会刷新到磁盘:

image-20220803222708620

我们可以发现:为了效率, 管道的数据并不会刷新到磁盘!,即 通信是在内存当中进行的


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

case1: 当客户端退出后,服务端将管道当中的数据读完后就再也读不到数据了,那么此时服务端也就会去执行它的其他代码了(在当前代码中是直接退出了)

image-20220804104730412

case2:当服务端退出后,客户端写入管道的数据就不会被读取了,也就没有意义了,那么当客户端下一次再向管道写入数据时,就会收到操作系统发来的13号信号(SIGPIPE),此时客户端就被操作系统强制杀掉了

image-20220804104829828


命名管道和匿名管道的对比

1)匿名管道由pipe函数创建并打开,命名管道由mkfifo函数创建,由open函数打开

2)为什么pipe叫做匿名管道,而fifo叫做命名管道呢?

  • 匿名管道文件不需要名字,因为它是通过父子继承的方式看到同一份资源, 命名管道一定要有名字, 从而使不相关进程定位同一个文件

3)命名管道和匿名管道一样,都是内存文件, 命名管道和匿名管道都不会将通信数据刷新到磁盘当中


命名管道也是基于字节流的, 所以进程通信的时候需要双方定制通信协议


命令行中的管道

在命令行当中的管道(“|”)到底是匿名管道还是命名管道呢

image-20220804105426260

由于匿名管道只能用于有亲缘关系的进程之间的通信,而命名管道可以用于两个毫不相关的进程之间的通信,因此我们可以先看看命令行当中用管道(“|”)连接起来的各个进程之间是否具有亲缘关系

下面我们通过管道| 连接3个进程: 通过ps命令可以查看这3个进程的信息

image-20220804105818828

这三个进程的PPID是相同的,也就是说它们是由同一个父进程创建的子进程,而它们的父进程实际上就是命令行解释器, Linux下这里为 bash

image-20220804105848434


也就是说,由管道(“|”)连接起来的各个进程是有亲缘关系的,它们之间互为兄弟进程

总结:若是两个进程之间采用的是命名管道,那么在磁盘上必须有一个对应的命名管道文件名,而实际上我们在使用命令的时候并不存在类似的命名管道文件名,因此命令行上的管道实际上是匿名管道

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...