Linux -- 命名管道

目录

前言:

命名管道

什么是命名管道?

怎么创建命名管道?

[mkfifo 函数](#mkfifo 函数)

在命令行中:

在代码中:

参数:

返回值:

[unlink 函数](#unlink 函数)

返回值:

验证命名管道

Comm.hpp

代码:

[为什么使用 c_str 函数?](#为什么使用 c_str 函数?)

[cerr 和 cout 的区别](#cerr 和 cout 的区别)

[#ifndef 的作用](#ifndef 的作用)

[读端 PipeServer.cc](#读端 PipeServer.cc)

代码:

[写端 PipeClient.cc](#写端 PipeClient.cc)

代码:

makefile

代码:

运行结果:


前言:

匿名管道可以让父子进程看到同一份资源,进行进程间通信,如果两个进程不是父子关系,也想进行进程间通信,该怎么办?

命名管道

进程间通信的一般规律是让不同的进程看到同一份资源,我们也可以让不是父子关系的进程看到同一个文件,和匿名管道一样,一个进程只打开该文件的读端,另一个进程只打开该文件的写端,这样就可以实现进程间通信,此时该管道称为命名管道。

什么是命名管道?

命名管道(Named Pipe),有时也称为FIFO(First In, First Out)文件,是一种特殊的文件类型,用于实现进程间通信(IPC, Inter-Process Communication)。与匿名管道不同,命名管道存在于文件系统中,并且有一个名称,因此可以被不相关的进程访问。这意味着即使两个进程没有直接的父子关系,它们也可以通过命名管道进行数据交换。

怎么创建命名管道?

命名管道中的数据不需要刷新到磁盘中,只是在两个进程之间通信,所以需要一个特殊的文件类型。

mkfifo 函数

在命令行中:

命令 mkfifo 管道名 就可以创建命名管道。

从上图中可以看出,创建出来的管道名后面会带 | ,且文件类型为 p,即管道文件
当我们向管道中写入数据时,如果没有进程以读方式打开管道,则尝试写入管道的操作将会阻塞,直到有进程开始读取为止。(命令执行起来就变成进程)

如下图:

没有进程读取管道中的数据,尝试写入管道的操作将阻塞:

当有进程读取管道中的数据时(读取如下图),写端进程就不会阻塞了:

下图同理:

同样地,如果没有进程写入管道,那么试图从管道读取的操作也会阻塞。

在代码中:
cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char*filename,mode_t mode);
参数:

filename 就是文件路径,mode 是管道文件的读写权限。

返回值:

若成功则返回 0,否则返回 -1,错误原因存于errno中。

由于管道文件存储在文件系统中,命名管道(FIFO)文件通常不需要手动删除,因为它们的行为依赖于打开它们的进程,生命周期也随进程。

然而,在某些情况下,你可能希望确保命名管道在不再需要时被立即删除,例如为了保持文件系统的整洁或避免遗留不必要的文件。这种情况下,你可以选择在程序退出之前显式地删除命名管道。这可以通过调用 unlink() 来完成

cpp 复制代码
#include<unistd.h>
int unlink(const char * pathname);
返回值:

**成功返回0,失败返回 -1,**错误原因存于errno中
如果文件已经被某个进程打开,那么即使调用了 unlink,只要这个文件描述符仍然被持有,文件内容就不会立即被释放,已经打开的文件描述符可以继续使用直到它们被关闭,但新的进程无法再通过这个路径名打开这个管道。只有当所有引用该文件的文件描述符都被关闭后,文件才会真正从磁盘上被删除。

验证命名管道

Comm.hpp

代码:

cpp 复制代码
#ifndef __COMMHPP__
#define __COMMHPP__

#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<string>
#include<cerrno>
#include<cstring>
#include<unistd.h>
#include<fcntl.h>
using namespace std;

#define Mode 0666
#define Path "./fifo"
class FIFO
{
public:
    FIFO(const string& path):_path(path)
    {
        int n=mkfifo(_path.c_str(),Mode);
        if(n==0)
        {
            //管道文件创建成功
            cout<<" mkfifo success "<<endl;
        }
        else
        {
            //管道文件创建失败,打印错误信息
            cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        }
    }

    ~FIFO()
    {
        int n=unlink(_path.c_str());
        if(n==0)
        {
            cout<<" remove success "<<endl;
        }
        else
        {
            cerr<<" remove failed,errno: "<<errno<<", errstring: " << strerror(errno) << endl;
        }
    }
private:
    string _path;
};

#endif

为什么使用 c_str 函数?

由于 mkfifo 的第一个参数类型是const char*,而我们传的参数是string类型,所以需要调用 string 中的c_str 函数,将 string 转为 const char* 类型

cerr 和 cout 的区别

默认情况下,cout 是缓冲的,这意味着数据不会立即被写入到屏幕上,而是等到缓冲区满或者遇到显式的刷新操作(如 std::endlstd::flush)时才会被实际输出。这样可以提高效率,减少频繁的I/O操作。
cerr 默认是非缓冲的,意味着每次调用都会立即输出到终端。这确保了即使程序崩溃,错误信息也能被及时看到。

#ifndef 的作用

#ifndef 是 C 和 C++ 预处理器指令之一,通常用于防止头文件的重复包含。它与 #define#endif指令一起使用来创建一个条件编译块。这个机制确保了一个头文件的内容在一个源文件中只被处理一次,从而避免了重复定义的问题。

基本语法如下:

cpp 复制代码
#ifndef SOME_UNIQUE_NAME
#define SOME_UNIQUE_NAME

// 头文件内容
// 这里可以放置类定义、函数声明等

#endif // SOME_UNIQUE_NAME

工作原理:

  • 当预处理器遇到 #ifndef SOME_UNIQUE_NAME 时,它会检查 SOME_UNIQUE_NAME 是否已经被定义。
  • 如果SOME_UNIQUE_NAME 尚未被定义,则执行从 #ifndef 到 #endif 之间的代码,并且在过程中通过 #define SOME_UNIQUE_NAME 定义这个标识符。
  • 如果SOME_UNIQUE_NAME 已经被定义(例如,由于该头文件之前已经被包含过),则 #ifndef 和 #endif 之间的所有内容将被忽略,即不会再次处理这些内容。

替代方法:

除了 #ifndef,还有其他几种方式也可以达到同样的效果,比如 #pragma once。这是一种非标准但广泛支持的方法,它的语法更加简洁:

cpp 复制代码
#pragma once

// 头文件内容

不过,#pragma once 并不是所有编译器都支持,因此对于需要高度可移植性的代码,还是推荐使用 #ifndef 方法。

读端 PipeServer.cc

代码:

cpp 复制代码
#include"Comm.hpp"
#include<unistd.h>

int main()
{
    //获取管道
    FIFO fifo(Path);

    //以读方式打开
    int rfd=open(Path,O_RDONLY);
    if(rfd<0)
    {
        //打开失败
        cerr<<" open failed,errno: "<<errno<<", errstring: "<<strerror(errno)<<endl;
        return 1;
    }
    cout<<" open success "<<endl;

    while(1)
    {
        char buffer[1024];
        size_t n=read(rfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            //读到了数据
            //由于我们没有读取 \0,所以在缓冲区的结束位置补上
            buffer[n]=0;
            cout<<"client say: "<<buffer<<endl;

        }
        else if(n==0)
        {
            //读到结束位置
            cout << "client quit, me too!!" << endl;
            break;
        }
        else
        {
            //读取失败
            cerr<<" read failed,errno: "<<errno<<",errstring: "<<strerror(errno)<<endl;
            break;
        }
    }

    //关闭文件描述符
    close(rfd);
    return 0;
}

写端 PipeClient.cc

代码:

cpp 复制代码
#include"Comm.hpp"

int main()
{
    int wfd=open(Path,O_WRONLY);
    if(wfd<0)
    {
        //以写方式打开失败
        cerr<<" open failed,errno: "<<errno<<",errstring: "<<strerror(errno)<<endl;
        return 1;
    }
   

    string buffer;
    while(1)
    {
        cout<<"Please enter your message: ";
        std::getline(cin,buffer);
        if(buffer == "quit")    break;
        ssize_t n= write(wfd,buffer.c_str(),buffer.size());
        if(n<0)
        {
            cerr<<" write failed,errno: "<<errno<<",errstring: "<<strerror(errno)<<endl;
            break;
        }

    }

    close(wfd);
    return 0;
}

makefile

代码:

因为 makefile 一次只能生成一个可执行程序,需要**.PHONY: all**让 makefile 一次生成两个可执行程序!

cpp 复制代码
.PHONY:all
all:pipe_server pipe_client

pipe_server:PipeServer.cc
	g++ -o $@ $^ -std=c++11
pipe_client:PipeClient.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f pipe_client pipe_server

运行结果:

当我们同时运行 pipe_client 和 pipe_server 时,结果如下(先执行 pipe_server,再执行 pipe_client,因为创建管道的代码在 pipe_server 中):

当我们在 pipe_client 输入信息时,pipe_server 就会接收到信息:

当我们输入 quit 时,两个程序都会退出:

退出的同时把管道文件删除!

当我们输入 CTRL+ C 时也会退出:

相关推荐
加载中loading...16 分钟前
Linux线程安全(二)条件变量实现线程同步
linux·运维·服务器·c语言·1024程序员节
安科瑞刘鸿鹏18 分钟前
校园建筑用电安全监测装置 电气火灾监测预防设备功能介绍
运维·服务器·网络·嵌入式硬件·安全·能源
课堂随想21 分钟前
【libGL error】Autodl云服务器配置ACT的conda虚拟环境生成训练数据时,遇到了libGL相关错误,涉及swrast_dri.so
运维·服务器·conda
画江湖Test1 小时前
SDK如何测试
服务器·sdk
well_fly3 小时前
Ubuntu特殊目录
linux·ubuntu
落落落sss3 小时前
es实现自动补全
大数据·服务器·elasticsearch·搜索引擎·全文检索
大熊程序猿3 小时前
ubuntu 安装k3s
linux·运维·ubuntu
luoqice3 小时前
CentOS 自启动某个应用
linux·运维·服务器
泠山3 小时前
ubuntu增加swap交换空间
linux·运维·ubuntu
JavaOpsPro4 小时前
jenkins部署手册
运维·jenkins·离线部署