Linux--进程间通信(2)(有名管道)

目录

1.原理

2.创建命名管道 

3.使用命名通道实现简单的通信

4.使用创建的命名管道 


1.原理

        匿名管道没有名称,它们是通过句柄在父进程和子进程之间传递的。这意味着匿名管道只能用于具有父子关系的进程之间。

        但如果程序之间没关系,那么这时候就要用到有名管道了,有名管道通过一个名称(通常是一个文件系统中的路径)来标识。这使得任何进程都可以通过该名称来访问管道,而不必是创建管道的进程的子进程。有名管道支持不同进程间的通信,甚至支持跨计算机(网络)的通信。有名管道的生命周期由创建它的进程控制,但即使创建它的进程终止,只要还有进程连接着管道,管道就会继续存在。

        命名管道在操作系统中表现为一种特殊类型的文件,它存在于系统的命名空间中,可以像打开文件那样被打开和读写。一旦创建,命名管道就可以在不同的进程中被打开多次,允许单向或双向的数据流传输。


2.创建命名管道 

创建命名管道,直接使用mkfifo命令就可以了

eg:

创建一个命名管道

        一号机上的while循环持续地将字符串"hello boy"写入到命名管道myfifo中,每次写入后暂停一秒。二号机上的cat命令则从myfifo中读取数据,并将其输出到标准输出。看一看效果:我们发现在一号机写到myfifo中的数据会被同步到二号机中的myfifo,

        两个不相关的进程(一号机和二号机上的进程)之间建立通信。这两个进程不需要有任何父子关系或其他特殊关系,只需要知道命名管道的文件路径即可。

        myfifo的文件大小始终都没有变,因为并没有被刷新到磁盘中。


3.使用命名通道实现简单的通信

提供一个关闭命名管道的函数:unlink

提供一个namepipe的类,它封装了命名管道的创建、打开、读写和删除的逻辑。以下是代码(namedPipe.hpp):

#pragma once

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

const std::string comm_path = "./myfifo";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096

class NamePiped
{
private:
    bool OpenNamedPipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if (_fd < 0)
            return false;
        return true;
    }

public:
    NamePiped(const std::string &path, int who)
        : _fifo_path(path), _id(who), _fd(DefaultFd)
    {
        if (_id == Creater)
        {
            int res = mkfifo(_fifo_path.c_str(), 0666);
            if (res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "creater create named pipe" << std::endl;
        }
    }
    bool OpenForRead()
    {
        return OpenNamedPipe(Read);
    }
    bool OpenForWrite()
    {
        return OpenNamedPipe(Write);
    }
    int ReadNamedPipe(std::string *out)
    {
        char buffer[BaseSize];
        int n = read(_fd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }
    int WriteNamedPipe(const std::string &in)
    {
        return write(_fd, in.c_str(), in.size());
    }
    ~NamePiped()
    {
        if (_id == Creater)
        {
            int res = unlink(_fifo_path.c_str());
            if (res != 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if(_fd != DefaultFd) close(_fd);
    }

private:
    const std::string _fifo_path;
    int _id;
    int _fd;
};
  1. 成员变量
    • _fifo_path:存储命名管道的路径。
    • _id:标识该对象是命名管道的创建者(Creater)还是用户(User)。
    • _fd:文件描述符,用于与命名管道进行通信。初始化为DefaultFd(定义为-1)。
  2. 构造函数
    • 接收命名管道的路径和创建者/用户标识。
    • 如果_idCreater,则调用mkfifo函数在指定路径下创建命名管道。如果创建失败,会打印错误信息。
  3. OpenForRead和OpenForWrite方法
    • 这两个方法分别用于打开命名管道进行读取和写入操作。
    • 内部调用OpenNamedPipe方法,传入相应的读取或写入模式(O_RDONLYO_WRONLY)。
    • OpenNamedPipe方法使用open系统调用来打开命名管道,并保存文件描述符到_fd成员变量中。
  4. ReadNamedPipe和WriteNamedPipe方法
    • ReadNamedPipe方法从命名管道中读取数据到提供的字符串指针中。
    • WriteNamedPipe方法将提供的字符串写入命名管道。
    • 这两个方法都使用readwrite系统调用来执行实际的读写操作。
  5. 析构函数
    • 在对象销毁时,析构函数会被调用。
    • 如果_idCreater,则调用unlink函数来删除命名管道。这确保了命名管道在不再需要时从文件系统中被移除。
    • 无论_id的值如何,都会检查_fd是否不是DefaultFd(即文件描述符是否已打开),如果是,则调用close函数来关闭文件描述符。

接下来创建一个客户端向命名管道写入(client.cc):

#include "namedPipe.hpp"

// write
int main()
{
    NamePiped fifo(comm_path, User);
    if (fifo.OpenForWrite())
    {
        std::cout << "client open namd pipe done" << std::endl;
        while (true)
        {
            std::cout << "Please Enter> ";
            std::string message;
            std::getline(std::cin, message);
            fifo.WriteNamedPipe(message);
        }
    }

    return 0;
}

循环的写入信息。


创建一个客户端用来读取命名管道的信息(server.cc):

#include "namedPipe.hpp"


int main()
{
    NamePiped fifo(comm_path, Creater);
    // 对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
    // 进程同步
    if (fifo.OpenForRead())
    {
        std::cout << "server open named pipe done" << std::endl;

        sleep(3);
        while (true)
        {
            std::string message;
            int n = fifo.ReadNamedPipe(&message);
            if (n > 0)
            {
                std::cout << "Client Say> " << message << std::endl;
            }
            else if(n == 0)
            {
                std::cout << "Client quit, Server Too!" << std::endl;
                break;
            }
            else
            {
                std::cout << "fifo.ReadNamedPipe Error" << std::endl;
                break;
            }
        }
    }

    return 0;
}
  • 如果ReadNamedPipe返回的值n大于0,表示成功读取了n个字符到message中。
    • 程序将输出"Client Say> "和读取到的消息内容。
  • 如果n等于0,通常表示客户端已经关闭了连接或者发送了一个EOF(文件结束符)。
    • 程序将输出"Client quit, Server Too!"并退出循环,然后退出程序。
  • 如果n小于0,表示读取过程中发生了错误。
    • 程序将输出"fifo.ReadNamedPipe Error"并退出循环,然后退出程序。

4.使用创建的命名管道 

 我们先运行了读端程序,但是并没有提示我们的读端创建成功(对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开)

打开写端,读端才成功打开。

这就实现进程间的通信了

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值