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给我们创建一个双方都能看见的文件。这两种只能解决简单的文件通信

相关推荐
funnycoffee1231 小时前
Cisco Firewpower 4100 9300 FXOS change management ip address
linux·数据库·tcp/ip
Agent手记1 小时前
多渠道订单数据处理自动化,落地步骤与ERP打通方案 | 2026企业级智能体实战手册
运维·人工智能·ai·自动化
呉師傅1 小时前
统信UOS如何安装本地打印机驱动以及URL查找网络打印机并安装驱动方法
运维·服务器·网络·windows·电脑
iFlow_AI1 小时前
构建自动化专属Paper库:用 Happy-Notes + iFlow-search-skill 搭建阅读写作流水线
运维·自动化·skill·心流·happy-notes
青梅橘子皮1 小时前
Linux---开发工具(1)(vim,gcc/g++)
linux·运维·服务器
邮专薛之谦1 小时前
Linux常用指令大全(完整版)
linux·运维·服务器
Ogcloud_oversea2 小时前
SD-WAN 技术架构解析:控制平面与数据平面的解耦实践
运维·网络·网络协议·网络安全·信息与通信
jfqqqqq2 小时前
记一次ubuntu 22.04安装旧版 MongoDB 4.2
linux·mongodb·ubuntu
BING_Algorithm2 小时前
开发常用Linux命令
linux·后端