Linux——命名管道

发布于:2024-05-06 ⋅ 阅读:(29) ⋅ 点赞:(0)

管道特点

  • 只能用于具有具体祖先的进程之间的通信,通常,一个管道由一个进程创建,然后该进程调用fork,创建子进程,关闭相应的读写端,然后父子进程就可以通信了
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的声明周期随进程
  • 一般而言,内核会对管道操作进行同步和互斥

在这里插入图片描述

创建命名管道

mkfifo :第一个参数是命名管道的路劲名,第二个参数是权限,mode_t 其实是对unsigned int 的封装
返回值:成功返回0,失败返回-1,错误码被设置
在这里插入图片描述

在这里插入图片描述

文件分类

  • -:普通文件
  • d:目录文件
  • b:块设备文件
  • c:字符文件
  • p:管道文件,上述图中所示
  • s:网络(socket)文件
  • l:链接文件

这时有一个问题,就是如何进行通信的。通信的前提就是看到同一份资源,只有看到同一份资源,才能进行通信。
而mkfifo一个库函数,就是通过第一个参数看到同一份资源的,我们都知道,每一个进程都有属于自己的PCB结构体,其中有一个指针指向了文件描述符表,文件描述符表的内容又指向了file结构体,其中file结构体中存放着文件的inode,属性信息等等

当另一个进程创建的时候,去访问同一份资源,最后的访问的文件内容其实只有一份,进程最后一访问的就是同一块缓冲区。

如保证两个不同的进程打开的是同一个文件??

在linux中 文件路径 + 文件名就可以保证打开的是同一个文件
在这里插入图片描述

在这里插入图片描述

实验:

管道是具有同步和互斥的,当我们读取一个管道文件的时候,如果没有文件就会被阻塞住,当有文件就会被读出来
在这里插入图片描述

这里我用客户端和服务端来实验, 当我们的服务端受到了客户端发来的消息,它会去读取,通过调用read函数,其中同步由read系统调用函数来解决。
在这里插入图片描述

ssize_t read(int fd,void buf,size_t count)*
功能:将fd指向的文件中的count个传送到buf中
如果返回0,表示已经到文件末尾或是无可读取的数据。如果返回-1,代表读取不成功,错误码存入errno中。
fd:文件描述符
buf:所要读的内容
count:要读取的大小

客户端打开管道文件是通过open系统调用函数,open函数中,flags可以是O_RDONLY、O_WRONLY、O_NONBLOCK,但是不能是O_RDWR,因为管道是单向的
在这里插入图片描述
成功打开则返回文件描述符,失败返回-1
其中flags 是以什么方式打开。
O_RDONLY 只读方式
O_WDONLY只写方式
O_RDWR读写方式
O_APPEND在文件的末尾上追加
O_CREAT如果文件不存在就创建
mode是权限

server.cc

#include "comm.hpp"
#include "log.hpp"
#include <sys/wait.h>

//这是从fd中拿到数据的函数
static void getMessage(int fd)
{
    char buffer[SIZE];
    while (true)
    {
        memset(buffer, '\0', sizeof(buffer));
        // 这里sizeof-1  是因为系统接口,文件有自己的管理机制,OS不用去关心\0
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            cout <<"[" << "pid:" << getpid() << "]" << " " << "client say:"<< " " << buffer << endl;
        }
        else if (s == 0)
        {
            cout << "[" << "pid:" << getpid() << "]"  << " "<< "read end of file, client quit,server quit too" << endl;
            break;
        }
        else
        {
            // read fail
            break;
        }
    }
}

int main()
{
    //1.创建管道文件
    if(mkfifo(ipcPath.c_str(),MODE) < 0)
    {
        perror("mkfifo fail");
        exit(1);
    }
    log("创建管道成功",Debug) << " | " <<  "Step 1" << endl;

    //2.正常的文件操作
    int fd = open(ipcPath.c_str(),O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(2);
    }
    log("打开管道成功",Debug)<< " | " << "Step 2" << endl;

    int nums = 5; 
    for(int i =0; i<nums; i++)
    {
        pid_t id = fork();
        if(id == 0)
        {
            //3.编写正常代码
            //child
            //从fd中拿到数据
            getMessage(fd);
            exit(1);
        }
    }

    for(int i =0; i<nums; i++)
    {
        //阻塞式等待
        waitpid(-1,nullptr,0);
    }


    //4.关闭文件
    close(fd);
    log("关闭管道成功",Debug)<< " | " << "Step 3" << endl;
    //通信结束,关闭管道文件
    unlink(ipcPath.c_str());
    log("删除管道成功",Debug) << " | "<< "Step 4" << endl;

    return 0;
}

client.cc

#include "comm.hpp"
#include "log.hpp"

int main()
{
    //1.获取管道文件
    int fd = open(ipcPath.c_str(),O_WRONLY);
    if(fd < 0) 
    {
        perror("open");
        exit(1);
    }

    // 2.ipc过程
    string buffer;
    while(true)
    {
        cout << "Please Enter Message Line ->";
        getline(cin,buffer);
        //向文件里面去写
        write(fd,buffer.c_str(),buffer.size());
    }


    //3.关闭文件
    close(fd);

    return 0;
}

实验结果:

在这里插入图片描述
最后的退出结果
在这里插入图片描述