前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Linux】深度探秘命名管道:Linux 进程通信的无声桥梁

【Linux】深度探秘命名管道:Linux 进程通信的无声桥梁

原创
作者头像
Yui_
发布2024-11-18 16:43:12
发布2024-11-18 16:43:12
13700
代码可运行
举报
文章被收录于专栏:Linux
运行总次数:0
代码可运行

0.好事发生

在文章开始之前,推荐一篇值得阅读的好文章!感兴趣的也可以去看一下,并关注作者!

如果你对机器学习感兴趣的话,今天的推荐文章可以帮你提高机器学习的效率。

文章链接:https://cloud.tencent.com/developer/article/2466294

通过这篇文章,可以帮助你深入的了解提升机器学习模型表现的三大核心技术。

🏠 大家好,我是Yui_,一位努力学习C++/Linux的博主~💬

1.什么是命名管道

在 Unix/Linux 系统中,管道(Pipe)是一种重要的进程间通信(IPC,Inter-Process Communication)机制。除了前面介绍的匿名管道(Anonymous Pipe),系统还提供了命名管道(Named Pipe),通常称为 FIFO(First In, First Out)。命名管道通过一个在文件系统中存在的路径名来标识,使得不相关的进程之间也能通过它进行通信。

命名管道是一种特殊类型的文件,它在文件系统中有一个明确的名称,可以被多个进程打开和访问。与匿名管道不同,命名管道不局限于具有亲缘关系的进程(如父子进程),任何具有访问权限的进程都可以通过命名管道进行通信。

<font color=red>我们可以把命名管道看成”挂名“的匿名管道,把匿名管道加入文件系统中,但仅仅是挂个名而已,目的是为了人其他进程也能看到也看到这个文件(文件系统中的文件可以被所有的进程看到)</font>

注意

<font color=gold>命名管道虽然能在文件系统被看到,但是它是没有Data block的,也就它不会存储在磁盘中,是一个内存文件。所以命名管道这个特殊文件大小为0</font>

1.2 命名管道的特点

  • 持久性:命名管道存在于文件系统中,直到被删除。即使创建它的进程退出,命名管道仍然存在,等待其他进程的连接。
  • 双向通信:虽然管道本质上是半双工的(单向),但通过两个命名管道可以实现全双工通信。
  • 跨进程通信:命名管道允许不相关的进程之间进行通信,只需知道管道的路径即可。
  • 无需父子关系:任何进程都可以打开命名管道进行读写,不需要继承关系。2. 创建命名管道创建命名管道有两种方法:
    1. 直接在命令行上创建。

在程序中创建。

在命令行创建:

代码语言:shell
复制
mkfifo mypipe

效果如下:

代码语言:shell
复制
ubuntu@VM-20-9-ubuntu:~/pipeTest/namePipe$ mkfifo mypipe
ubuntu@VM-20-9-ubuntu:~/pipeTest/namePipe$ ls -l
total 0
prw-rw-r-- 1 ubuntu ubuntu 0 Nov 15 19:56 filename
prw-rw-r-- 1 ubuntu ubuntu 0 Nov 15 20:21 mypipe

2.1 在C程序中创建命名管道

为了在C程序中创建命名管道,我们需要用到的函数也是mkfifo

<font color=red>mkfifo:</font>

头文件:

代码语言:c
代码运行次数:0
复制
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

格式:

代码语言:c
代码运行次数:0
复制
int mkfifo(const char *pathname, mode_t mode);

参数介绍:

  • pathname
    • 命名管道的路径名。
    • 在文件系统中创建一个文件,代表命名管道。
  • mode - 管道文件的权限(类似于文件的权限),是一个 mode_t 类型值。 - 常见值: - 例如 0666,允许所有用户读写。 - mode 会受到 进程的 umask 设置 的影响。 为了防止mode的设置被umask影响,可以事先将umsak设置为0,umask(0). 返回值: 成功:0 失败:-1,并设置error 下面我来实现一个进程向另一个进程发送信息: 客户端:client.cc
代码语言:cpp
代码运行次数:0
复制
//客户端向服务端发送消息
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <fcntl.h>
#include "common.hpp"

using namespace std;

int main()
{
    int mf = mkfifo(pipePath,md);
    if(mf<0)
    {
        perror("mkfifo");
        exit(1);
    }
    //打开管道
    int fd = open(pipePath,O_WRONLY);//以写方式打开
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    //char data[SIZE];
    string data;
    while(true)
    {
        //开始写入数据
        cout<<"cilent message# ";
        getline(cin,data);
        if(data == "exit")
            break;
        ssize_t n = write(fd,data.c_str(),data.size());
        if(n<0)
        {
            perror("write");
            exit(1);
        }

    }
    //退出客户端
    cout<<"exit"<<endl;
    close(fd);

    return 0;
}

服务端:server.cc

代码语言:cpp
代码运行次数:0
复制
//服务端接受客户端的信息
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <fcntl.h>
#include "common.hpp"

using namespace std;

int main()
{
    //打开管道
    int fd = open(pipePath,O_RDONLY);//以读的方法
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    char data[SIZE];
    while(true)
    {
        int n = read(fd,data,sizeof(data)-1);
        if(n<0)
        {
            perror("read");
            exit(1);
        }
        else if(n == 0)
        {
            //写端关闭
            cout<<"写端关闭"<<endl;
            break;
        }
        data[n] = 0;
        cout<<"client say:"<<data<<endl;
    }
    
    close(fd);
	if (unlink(pipePath) == -1) {
	perror("unlink failed");
	return 1;
    }//关闭管道
    return 0;
}

公共文件:common.hpp

代码语言:cpp
代码运行次数:0
复制
#pragma once
#define SIZE 1024
//存储管道的路径+名字
const char* pipePath = "./myPipe";
//也可以用绝对路径,当然相对路径更简单

//设置mask
mode_t md = 0666; 

效果图:

3. 命名管道的工作原理

再次回到文件系统:当重复多次打开一个文件时,并不会费力的打开多次,而是在第一次的基础上对struct_file结构体中的引用计数自增1,所以对于同一个文件,不同的进程打开了,看到的就是同一个。

这也是管道实现的本质:<font color=red>让不同的进程看到同一块空间。</font>

因为命名管道适用于独立的进程IPC,所以无论是读端还是写端,进程A,进程B为其分配的文件描述符都是3。

我们知道如果是匿名管道,因为是依靠继承才看到同一文件的,所以读写端的fd是不一样的。

3.1 命名管道和匿名管道的区别与联系

3.1.1 命名管道与匿名管道的区别

  • 匿名管道只能用于具有血缘关系的进程间通信;而命名管道就不一样了,无论有没有血缘关系都可以。#pragma once #include <unistd.h> #include <sys/types.h> #include <cstdlib> #include <iostream> #include <sys/stat.h> #include <fcntl.h> #include <cstring> #define SIZE 1024 //打开拷贝的文件路径 const char* filePath = "./file.txt"; //要打开的管道路径 const char* pipePath = "./pipe"; //mask码设置 mode_t md = 0666;
  • 匿名管道是通过pipe函数创建出来了;而命名管道需要先通过mkfifo函数创建,然后再通过open打开使用。
  • 当出现多条匿名管道时,可能会出现写端fd被重复继承的情况;而命名管道就不会出现这种情况。3.1.2 命名管道和匿名管道的联系
  • 两个都是属于管道,都是操作系统中最古老的进程通信方式,都自带有同步和异步机制,提供的都是流式数据传输 与匿名管道相同的也有在这4个场景下的处理
  • 管道为空时,读端堵塞,等待写端写入数据。
  • 管道已满时,写端堵塞,等待读端读取数据。
  • 进程通信时,关闭读端,OS发出13号信息SIGPIPE终止写端进程。
  • 进程通信时,关闭写端,读端读取到0字节数据,可以凭借这个特征来终止进程。<font color=gold>4.命名管道的简单应用</font>我们可以利用命名管道来实现一些简单的功能,加强我们对命名管道的理解。现在我们打算实现的文件拷贝小程序。 我们可以利用命名管道实现不同进程间IPC,也就是一个进程读取文件中的内容然后写进管道当中,然后另一个进程在通过管道将数据读出保存到新的文件,如此一来就是实现了一个进程的文件拷贝功能。 这也是在网络上下载应用的方式,因为下载应用的本质就是下载文件,我们将服务器看作写端,自己的电脑看作读端,那么下载这个动作的本质就是IPC,不过是在网络层面实现的。 公共区域common.hpp

服务端server.cc

代码语言:cpp
代码运行次数:0
复制
/**
 * 服务端通过命名管道将本地文件拷贝到客户端
 * 先打开命名管道,再打开需要被拷贝的文件,将文件通过命名管道发送给另一个程序
 * 
 */

#include "common.hpp"

int main()
{
    //创建命名管道
    if(mkfifo(pipePath,md) == -1)
    {
        perror("mkfifo");
        exit(1);
    }
    //打开管道
    int fd = open(pipePath,O_WRONLY);
    if(fd == -1)
    {
        perror("open");
        exit(1);
    }
    //打开需要被拷贝的文件
    FILE* fp = fopen(filePath,"r");
    if(fp == nullptr)
    {
        perror("fread");
        exit(1);
    }
    //将文件传给管道
    char buf[SIZE];
    size_t bytes = 0;
    while(true)
    {
        size_t n = fread(buf,1,sizeof(buf),fp);
        if(n>0)
        {
            //写入管道
            ssize_t sst = write(fd,buf,n);
            if(sst == -1)
            {
                perror("write");
                close(fd);
                fclose(fp);
                exit(1);
            }
            bytes+=n;
        }
        // 检查是否到达文件末尾或出错
        if (n < sizeof(buf)) {
            if (feof(fp)) {
                break; // 文件读取完毕
            }
            if (ferror(fp)) {
                perror("fread");
                fclose(fp);
                close(fd);
                exit(1);
            }
        }
    }
    std::cout<<"传递字节数"<<bytes<<std::endl;
    fclose(fp);
    close(fd);
    unlink(pipePath);
    return 0;
}

客户端client.cc

代码语言:cpp
代码运行次数:0
复制
//客户端接受服务端的文件
/**
 * 客户端接受服务端的文件
 * 打开命名管道,开始读取服务端传递给客户端的信息
 */
#include "common.hpp"

int main()
{
    
    //打开管道,读取管道数据
    int fd = open(pipePath,O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        exit(1);
    }
    
    //将读取的数据打印到显示屏
    char buf[SIZE];
    ssize_t n = read(fd,buf,sizeof(buf)-1);
    if(n == -1)
    {
        perror("read");
        exit(1);
    }
    std::cout<<"文件内容:\n"<<buf<<"读取字节数:"<<n<<std::endl;

    close(fd);
    return 0;

}

运行结果:

此时服务端是写端,客户端是读端,实现的是下载服务;当服务端是读端,客户端是写端是,实现的就是上传服务,搞两条管道就能实现模拟简单的数据双向传输服务。

注意:创建管道文件后,无论先启动读端还是启动写端,都要堵塞式的等待另一方进行交互。

5.总结

作为匿名管道的兄弟,命名管道具备匿名管道的大部分特性,使用方法也基本一致,不过二者在创建和打开方式上各有不同:匿名管道简单,但只能用于具有血缘关系进程间通信,命名管道虽麻烦些,但适用于所有进程间通信场景。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0.好事发生
  • 1.什么是命名管道
    • 1.2 命名管道的特点
    • 2.1 在C程序中创建命名管道
  • 3. 命名管道的工作原理
    • 3.1 命名管道和匿名管道的区别与联系
      • 3.1.1 命名管道与匿名管道的区别
  • 5.总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档