【Linux】命名管道&命名管道和匿名管道的对比&命令行中的管道_程序中使用管道,跟命令行中管道一样吗-程序员宅基地

技术标签: Linux  运维  linux  服务器  

命名管道

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

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

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

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


在程序中创建命名管道使用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 <sys/types.h>
#include <sys/stat.h>
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 client

server:server.c
	gcc -o $@ $^
client:client.c
	gcc -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<stdio.h>
#include<sys/stat.h>
#include<sys/stat.h> 
#include<sys/types.h> 
#include<fcntl.h> 
#include<unistd.h> 

#define MY_FIFO "./fifo"  //管道的创建路径
server.c

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

#include"comment.h"

int main()
{
    
    umask(0);将文件默认掩码设置为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;//字符串末尾置\0
            printf("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<string.h>    //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<string.h>  //strcmp函数
#include<stdlib.h>  //exit函数
#include<sys/wait.h>    //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;//字符串末尾置\0
            if(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


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

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chuxinchangcun/article/details/129287293

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签