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

相关推荐
爱讲故事的18 小时前
操作系统第一讲复习:为什么学习操作系统,以及操作系统到底在做什么?
linux·开发语言·windows·学习·ubuntu·c#
荒--18 小时前
kali安装与下载、设置(2026)
linux·服务器
Yang961118 小时前
一站式网络检测 鼎讯信通网络综合测试仪科普
运维·服务器·网络·能源
越强越不秃18 小时前
大模型驱动的PoC脚本自动化生成:从挑战到实践
运维·自动化·安全工程师
sulikey19 小时前
个人Linux操作系统学习笔记4 - makefile
linux·makefile·make·构建
_童年的回忆_19 小时前
【php】在linux下PHP安装amqp扩展
linux·开发语言·php
sxlishaobin19 小时前
linux 自动清除日志 脚本
linux·服务器·前端
杰克逊的日记19 小时前
K8s+GPU+大模型运维主要技术点
运维·容器·kubernetes
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ19 小时前
nginx部署教程
运维·网络·nginx
b***251119 小时前
电池组PACK自动化生产线:从电芯到成品的精密制造之路
运维·自动化·制造