Linux —— 进程间通信 - 命名管道

1. 命名管道的原理

命名管道是一种特殊类型的文件。

2. 命名管道的操作(1.命令级 2.代码级)

2.1 命令级

命名管道首先的有文件,创建命名管道,命令:mkfifo

管道的本质就是一个队列:

文件类型为p:是一个管道文件

这个就叫做管道。

说明对管道文件进行重定向,文件大小一直为0,本质上文件内容没有刷新到磁盘里,是在内存中,启动另一个进程,OS瞬间创建PCB,文件描述符表,页表等等,本质是从内存中读取,结束之后该管道的文件大小依旧为0

unlink fifo 删除管道文件 fifo

2.2 代码级

需要两个进程,二者可以毫无关系

bash 复制代码
#Makefile

.PHONY:all
all:server client

client:client.cpp
	g++ -o $@ $^ -std=c++11
server:server.cpp
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf client
	rm -rf server

server 用代码创建出一个管道:

client 和 server 要看见同一份资源:common.hpp

cpp 复制代码
//common.hpp

#ifndef __COMMON_HPP__
#define __COMMON_HPP__

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>

const std::string fifoname = "fifo";
mode_t mode = 0666; 

#endif
cpp 复制代码
//server.cpp

#include "common.hpp"


int main()
{
    int n = mkfifo(fifoname.c_str(), mode);
    if(n == 0)
    {
        std::cout << "mkfifo successs" << std::endl;
    }
    else
    {
        std::cout << "mkfifo failed" << std::endl;
    }
    return 0;
}

所以该管道文件的权限是:664

再次运行server的话,就会发现创建失败:

为什么失败呀?输出一下:(因为这个文件已经存在)

从上面的运行结果中,我们可以知道:创建管道的时候有可能会失败的。所以还得让server文件将管道释放掉:

cpp 复制代码
//server.cpp

#include "common.hpp"


int main()
{
    int n = mkfifo(fifoname.c_str(), mode);
    if(n == 0)
    {
        std::cout << "mkfifo successs" << std::endl;
    }
    else
    {
        // std::cout << "mkfifo failed" << std::endl;
        perror("mkfifo");
    }

    sleep(5);
    
    int m = unlink(fifoname.c_str());
    (void)m;
    return 0;
}

至此,命名管道就被创建出来了,也能被我们删除,server端,client端都能被看见,server、client都以文件形式打开,以文件形式读写就能完成通信了。

bash 复制代码
#Makefile

.PHONY:all
all:server client

client:client.cpp
	g++ -o $@ $^ -std=c++11
server:server.cpp
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf client
	rm -rf server
cpp 复制代码
// conmmon.hpp

#ifndef __COMMON_HPP__
#define __COMMON_HPP__

#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

const std::string fifoname = "fifo";
mode_t mode = 0666; 
// int size = 128;
#define SIZE 128

#endif
cpp 复制代码
// client.cpp

#include "common.hpp"


//客户端不需要创建管道文件

int main()
{
    int fd = open(fifoname.c_str(),O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }

    std::string message;
    while(true)
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin,message);
        write(fd,message.c_str(),message.size());
    }

    close(fd);
    return 0;
}
cpp 复制代码
//server.cpp

#include "common.hpp"


int main()
{
    // 1. 创建管道文件
    int n = mkfifo(fifoname.c_str(), mode);
    if(n == 0)
    {
        std::cout << "mkfifo successs" << std::endl;
    }
    else
    {
        // std::cout << "mkfifo failed" << std::endl;
        perror("mkfifo");
        exit(1);
    }

    // 2. 打开文件,就和普通文件没有区别
    // 命名管道操作特点:打开一端口,在另一端没有打开时候,open会阻塞
    //server --- 读
    int fd = open(fifoname.c_str(),O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(2);
    }
    std::cout <<" open file sucess" << std::endl;

    // 3. 通信
    // char buffer[size];  //支持,编译器比较新,一般支持变长数组;Linux的C语言标准是C99,C语言种类是GNUC,能编过的
    char buffer[SIZE];     //遵守之前的规则
    while(true)
    {
        buffer[0] = 0;  //清空字符串,O(1)时间复杂度
        ssize_t num = read(fd,buffer,sizeof(buffer)-1);
        if(num > 0)
        {
            buffer[num] = 0;
            std::cout << "client say#" << buffer << std::endl;
        } 
        std::cout << "num: " << num << std::endl;
    }

    // 4. 归还资源
    close(fd);

    int m = unlink(fifoname.c_str());
    (void)m;
    return 0;
}

细节说明:

可以看见运行server之后,只显示了 mkfifo sucess 的信息,没有输出上面的字符串,不是管道没有打开,而是因为命名管道有一个操作上的特点:打开一端,在另一端没有打开时候,open会阻塞。

当我们写端client关闭,server端读到的就是0:

所以对server端进行补充:

cpp 复制代码
while(true)
    {
        buffer[0] = 0;  //清空字符串,O(1)时间复杂度
        ssize_t num = read(fd,buffer,sizeof(buffer)-1);
        if(num > 0)
        {
            buffer[num] = 0;
            std::cout << "client say#" << buffer << std::endl;
        } 
        else if(num == 0)
        {
            std::cout << "client quit,me too!" << std::endl;
            break;
        }
        else
        {
            break;
        }
        std::cout << "num: " << num << std::endl;
    }

命名管道的4种情况和匿名管道的四种情况是一样的。

**命名管道:主要解决,毫无关系的进程之间,进行文件级进程通信!!!**剩下的特点和匿名管道的特点是一样的。

2.3 将以上代码进行调整一下,用C++面向对象的方式:

为什么不把close 和 remove 写进析构函数中,因为将来客户端只关闭不删除,服务器是既关闭又删除,所以分开写比较好。

cpp 复制代码
//common.hpp

#ifndef __COMMON_HPP__
#define __COMMON_HPP__

#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

const std::string fifoname = "fifo";
mode_t mode = 0666; 
// int size = 128;
#define SIZE 128

#endif
cpp 复制代码
//NamedPipe.hpp

#pragma once

#include "common.hpp"

const int defaultfd = -1;

class NamedPipe
{
public:
    NamedPipe(const std::string &name) : _name(name), _fd(defaultfd)
    {
    }
    bool Creat()
    {
        // 1. 创建管道文件
        int n = mkfifo(fifoname.c_str(), mode);
        if (n == 0)
        {
            std::cout << "mkfifo success " << std::endl;
        }
        else
        {
            // std::cout << "mkfifo failed "<<std::endl;
            perror("mkfifo");
            return false;
        }
        return true;
    }

    void Close()
    {
        if (_fd == defaultfd)
            return;
        else
            close(_fd);
    }

    bool OpenForRead()
    {
        // 2. 打开文件,就和普通文件没有区别
        // 命名管道操作特点:打开一端口,在另一端没有打开时候,open会阻塞
        // server --- 读
        _fd = open(fifoname.c_str(), O_RDONLY);
        if (_fd < 0)
        {
            perror("open");
            return false;
        }
        std::cout << " open file sucess" << std::endl;
        return true;
    }

    bool OpenForWrite()
    {
        _fd = open(fifoname.c_str(), O_WRONLY);
        if (_fd < 0)
        {
            perror("open");
            return false;
        }
        return true;
    }

    // 输入参数:connst &
    // 输出参数:*
    // 输入输出参数:&
    bool Read(std::string *out)
    {
        // 3. 通信
        // char buffer[size];  //支持,编译器比较新,一般支持变长数组;Linux的C语言标准是C99,C语言种类是GNUC,能编过的
        char buffer[SIZE] = {0};     // 清空字符串,O(1)时间复杂度
        ssize_t num = read(_fd, buffer, sizeof(buffer) - 1);
        if (num > 0)
        {
            buffer[num] = 0;
            *out = buffer;
        }
        else if (num == 0)
        {
            return false;
        }
        else
        {
            return false;
        }
        return true;
    }
    void Write(const std::string &in)
    {
        write(_fd,in.c_str(),in.size());
    }
    void Remove()
    {
        int m = unlink(fifoname.c_str());
        (void)m;
    }
    ~NamedPipe()
    {

    }


private:
    std::string _name;
    int _fd;
};
cpp 复制代码
//server.cpp

#include "NamedPipe.hpp"


int main()
{
    NamedPipe named_pipe(fifoname);
    named_pipe.Creat();
    named_pipe.OpenForRead();

    // 3. 通信
    std::string message;
    while(true)
    {
        bool res = named_pipe.Read(&message);
        if(!res)
            break;
            std::cout << "client say# " << message << std::endl;
    }

    // 4. 归还资源
    named_pipe.Close();
    named_pipe.Remove();

    return 0;
}
cpp 复制代码
//client.cpp

#include "NamedPipe.hpp"

// 客户端不需要创建管道文件

int main()
{
    NamedPipe named_pipe(fifoname);
    named_pipe.OpenForWrite();

    while (true)
    {
        std::cout << "Please Enter# ";
        std::string line;
        std::getline(std::cin, line);

        named_pipe.Write(line);
    }

    named_pipe.Close();
    return 0;
}

3. 总结

命名管道:主要解决,毫无关系的进程之间,进行文件级进程通信!!!

剩下的特点,匿名管道和命名管道特点相同。

前面的这两种通信方案都是文件级的,进程间通信。但是依旧有内核参与的。OS给我们创建一个双方都能看见的文件。这两种只能解决简单的文件通信

相关推荐
阿里云云原生8 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能
k4m7v2pz9 小时前
把 Bevy 0.14 游戏移植到 R36S 掌机:一场与“无窗口系统“的搏斗
linux·rust·bevy·r36s·rk3326·开源掌机
阿童木写作9 小时前
Python实现Temu图片批量翻译自动化教程
运维·人工智能·python·自动化
iori97king10 小时前
库存不足自动通知:第一次使用自动化
运维·自动化
@insist12310 小时前
信息系统管理工程师-信息系统运维资源、技术与智能运维核心考点解析
运维·软考·信管·软件水平考试·信息系统管理工程师
木子欢儿10 小时前
Intel 架构的 MacBook Pro 运行 Linux 发热量高解决
linux·运维·服务器
KKKlucifer12 小时前
运营商全域安全运维支撑体系落地实践
运维·安全
EAIReport13 小时前
工业电气自动化数据治理破局:多Agent大模型赋能全景智能决策
大数据·运维·自动化
清水迎朝阳13 小时前
客户端软件 — 用户统计方案
服务器·c++·客户端·用户统计
会编程的土豆13 小时前
Go 模板初识
linux·运维·服务器