当前位置: 首页 > article >正文

【Linux】进程间通信——命名管道

请添加图片描述

文章目录

  • 命名管道
    • 什么是命名管道
    • **命名管道 vs. 无名管道**
    • 如何创建命名管道
  • 用命名管道实现进程间通信
    • Makefile
    • Comm.hpp
    • Server.hpp
    • Client.hpp
    • Server.cpp
    • Client.cpp
  • 效果
  • 总结

命名管道

什么是命名管道

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


命名管道 vs. 无名管道

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

如何创建命名管道

在这里插入图片描述
手动创建命名管道:

mkfifo FIFO

在这里插入图片描述
这个FIFO也是一个文件,被操作系统特殊标记过,是管道文件。
在C语言库中有一个函数也是mkfifo,这个接口解决了进程间通信的问题。
在这里插入图片描述

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

在这里插入图片描述

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

Makefile

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

#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

#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

#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

#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

#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 机制,适用于轻量级的数据传输需求,在系统编程和日常应用中都有着广泛的应用。

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


http://www.kler.cn/a/565379.html

相关文章:

  • Python解决“比赛配对”问题
  • 爱普生SG-8101CE可编程晶振赋能智能手机的精准心脏
  • Redis 源码分析-内部数据结构 SDS
  • 在VSCode中使用MarsCode AI最新版本详解
  • 12. 三昧真火焚环劫 - 环形链表检测(快慢指针)
  • 【新手入门】SQL注入之盲注
  • 一周掌握Flutter开发--5、网络请求
  • JavaWeb后端基础(2)
  • 【Qt】为程序增加闪退crash报告日志
  • Python—Excel全字段转json文件(极速版+GUI界面打包)
  • spring结合mybatis多租户实现单库分表
  • Three.js 入门(几何体不同顶点组、设置不同材质、常见几何体)
  • CDN与群联云防护的技术差异在哪?
  • Java内存的堆(堆内、堆外)、栈含义理解笔记
  • 端口映射/内网穿透方式及问题解决:warning: remote port forwarding failed for listen port
  • 机器学习(模型的保存和加载)
  • 【版本控制安全简报】Perforce Helix Core安全更新:漏洞修复与国内用户支持
  • nginx 动态计算拦截非法访问ip
  • 【Linux】ubuntu server扩容硬盘
  • 树莓百度百科更新!宜宾园区业务再添新篇