【Linux】管道通信——命名管道

文章目录

命名管道

什么是命名管道

命名管道,也称为 FIFO(First In First Out) ,是一种 进程间通信(IPC) 机制,它允许不相关的进程(即没有父子关系的进程)通过文件系统中的特殊文件进行数据传输。


命名管道 vs. 无名管道

类型 说明 适用场景
匿名管道 pipe() 创建,仅限于父子进程之间通信 适用于父进程创建子进程并通信
命名管道 mkfifo() 创建,存在于文件系统中,可用于任意进程间通信 适用于独立进程间通信

如何创建命名管道

手动创建命名管道:

cpp 复制代码
mkfifo FIFO

这个FIFO也是一个文件,被操作系统特殊标记过,是管道文件。

在C语言库中有一个函数也是mkfifo,这个接口解决了进程间通信的问题。

用命名管道实现进程间通信

我们创建一下文件,Client是模拟的客户端用于发送请求,server是服务端,用于接收请求,Comm用于存储全局变量,还有一些都要用到的函数,Makefile用来编译可执行程序,首先我们将Makefile完善。

Makefile

cpp 复制代码
SERVER=server 
CLIENT=client 
cc=g++
SERVER_SRC=Server.cc
CLIENT_SRC=Client.cc

.PHONY:all
all:$(SERVER) $(CLIENT)

$(SERVER):$(SERVER_SRC)
	$(cc) -o $@ $^ -std=c++11
$(CLIENT):$(CLIENT_SRC)
	$(cc) -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f $(SERVER) $(CLIENT)

由于在Makefile中只能创建一个可执行文件,所以我们需要加上这一段:

这一段表示all,这个伪目标依赖下面两个可执行程序,所以不得不创建下面两个可执行程序来完成这个伪目标了。

Comm.hpp

cpp 复制代码
#pragma once

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

//双方通信使用的文件
const string gpipefile = "./fifo";
const mode_t gmode = 0600;//允许拥有者写,所属组和other什么权限都没有
const int gdefulted = -1;
const int gsize = 1024;
const int gForRead = O_RDONLY;
const int gForWrite = O_WRONLY;

//打开管道
int OpenPipe(int flag)
{
    //以只读方式打开
    int fd = open(gpipefile.c_str(),flag);
    if(fd < 0)
    {
        cerr<<"open error"<<endl;
        return false;
    }
    return fd;
}
void ClosePipeHelper(int fd)
{
    if(fd >= 0) close(fd);
}

由于客户端和服务器都要打开管道和关闭管道,所以我们将这两个接口放在Comm中,但是因为这两个端的打开方式不一样,所以我们用一个参数来代替,调用这个函数的时候只需要传递调用的方式就行了。

Server.hpp

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

class Init
{
public:
    //创建文件
    Init()
    {
        umask(0);//先将缺省权限清零
        int n = mkfifo(gpipefile.c_str(),gmode);
        if(n < 0)//创建管道文件失败
        {
            cerr<<"mkfifo error"<<endl;
            return;
        }
        cout<<"mkfifo success"<<endl;
    }
    //销毁文件
    ~Init()
    {
        //删除当前路径下的文件
        int n = unlink(gpipefile.c_str());
        //判断是否删除成功
        if(n < 0)
        {
            cerr<<"unlink error"<<endl;
            return;
        }
        cout<<"unlink success"<<endl;
    }
};

Init init;

class Server
{
public:
    //初始化
    Server():_fd(gdefulted){}
    bool OpenPipeForRead()
    {
        _fd = OpenPipe(gForRead);
        if(_fd < 0)return false;
        return true;
    }
    //读取管道
    //string *out:输出型参数
    //const string &输入型参数
    //string &输入输出型参数 
    int RecvPipe(string *out)
    {
        char buffer[gsize];
        //读取文件
        ssize_t n = read(_fd,buffer,sizeof(buffer)-1);//预留一个空间,后面是期望读这么多,n是实际读取的字节数
        if(n > 0)//读取成功
        {
            buffer[n]=0;
            //读取成功将buffer带出去
            *out = buffer;
        }
        //返回read实际的字节数
        return n;
    }
    void ClosePipe()
    {
        ClosePipeHelper(_fd);
    }
    ~Server()
    {}
private:
    int _fd;
};

首先是创建管道文件,我们封装一个类,用于管理管道文件的创建和销毁,声明一个全局变量,构造函数用于创建管道,析构函数用于销毁管道,由于全局变量的生命周期是和程序一样的,所以当程序结束的时候管道文件也跟着销毁,也意味着通信结束。


Server是客户端用于接收请求,意思是我们要将Client发送的信息输出到屏幕上。

这个函数就是用于读取管道内部客户端发送的请求。

Client.hpp

cpp 复制代码
#pragma once
#include "Comm.hpp"//看到同一份资源

class Client
{
public:
    //初始化
    Client():_fd(gdefulted){}
    //打开管道
    bool OpenpipeForWrite()
    {
        _fd = OpenPipe(gForWrite);
        if(_fd < 0) return false;
        return true;
    }
    //发送管道
    int SendPipe(const string& in)
    {
        return write(_fd,in.c_str(),sizeof(in));
    }
    //关闭管道
    void ClosePipe()
    {
        ClosePipeHelper(_fd);
    }
    ~Client(){}
private:
    int _fd;
};

Client唯一和Server不一样的就是一个是读取,一个是发送,而Client就是发送,向服务器发送信息。

所以这里直接向管道中写即可。

string *out:输出型参数 | const string &输入型参数 | string &输入输出型参数

上面是每个参数的意义

Server.cpp

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


int main()
{
    Server server;
    server.OpenPipeForRead();
    string message;
    while(true)
    {
        if(server.RecvPipe(&message) > 0)
        {
            cout<<"client Say#"<<message<<endl;
        }
        else break;
    }
    cout<<"client quit,me too"<<endl;
    server.ClosePipe();
    return 0;
}

Client.cpp

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


int main()
{
    Client client;
    client.OpenpipeForWrite();
    string message;
    while(true)
    {
        cout<<"please Enter#";
        getline(cin,message);
        client.SendPipe(message);//这里的message从标准输入来
    }
    client.ClosePipe();
    return 0;
}

效果

当客户端关闭时服务器也会跟着关闭

总结

命名管道(FIFO)作为 Linux 进程间通信(IPC)的一种机制,提供了一种基于文件系统的 数据传输方式,使得不相关进程 之间也能进行数据交换。相比于无名管道,它具有更高的灵活性,不需要父子进程关系,适用于生产者-消费者模式日志收集进程调试等场景。

通过 mkfifo 创建命名管道,我们可以实现进程间的数据流动,而不必使用共享内存或消息队列等复杂机制。命名管道不仅支持流式数据传输,还能够跨终端、跨进程进行数据交互,极大简化了进程间通信的实现。

总结来说,命名管道是一种简单、高效、灵活的 IPC 机制,适用于轻量级的数据传输需求,在系统编程和日常应用中都有着广泛的应用。

通过实践,我们也看到了命名管道的易用性与强大功能,它使得开发者能够更加高效地实现进程间的数据交换,促进了软件系统的模块化与解耦。

相关推荐
EasyNVR20 分钟前
轻量级SDK,大能量:EasyRTC重塑嵌入式设备音视频体验
运维·服务器·音视频·webrtc·p2p·智能硬件
老友@1 小时前
Docker 部署 OnlyOffice 文档服务器
运维·服务器·后端·docker·容器·编辑器·onlyoffice
Koma_zhe1 小时前
【搭建SigNoz性能监控平台】在Ubuntu上快速搭建高效的SigNoz性能监控平台与远程使用技巧
linux·运维·ubuntu
xxxx1234451 小时前
Ubuntu中出现对control.tar.zst未知压缩
linux·运维·ubuntu
淳杰1 小时前
ubuntu部署小笔记-采坑
linux·笔记·ubuntu
Mr.Wang8091 小时前
条款24:若所有参数皆需类型转换,请为此采用 non-member 函数
开发语言·c++
茂茂在长安1 小时前
Linux 命令大全完整版(12)
linux·运维·服务器·数据库·运维开发
技匠而已2 小时前
ubuntu安装docker & docker/DockerHub 国内镜像源/加速列表【持续更新】
linux·ubuntu·docker
不会玩技术的技术girl2 小时前
深入解析淘宝订单接口:设计、调用与安全实践
服务器·数据库·安全
WIFI_BT_DEV2 小时前
Linux设备驱动开发-中断
linux·c语言·arm开发·驱动开发·嵌入式硬件·硬件架构·gnu