Linux:进程间通信——匿名管道通信、命名管道通信

一、通信的概念

很容易理解就是两个进程可以相互进行数据的输入输出,对一块资源的共同使用,或者是一个进程让另外一个进程执行不同任务等都是通信的目的
我们知道,进程是有独立性的,那肯定不可能是在自己的进程内部的资源,因为其它进程看不到,所以一定是有一块公共空间,能让不同进程进行通信

二、管道通信

就是我们以前命令用过的 比如 ls | head -1 的竖划线就是管道,为什么叫管道?因为管道通信都是单向通信,一个进程只能写入数据,另外一个只能读数据像我们现实中管道一样,一边工厂给你输入水,你自己用水龙头使用。

2.1匿名管道

特点:严格的单向通信(一个进程只做输入或输出)
管道结构:它其实是一个内存级的file文件,也就是将来的数据不会刷新到磁盘上。file文件中的内核缓冲区就是管道,它与普通文件的区别就是它没有名字(也就是没有路径),其他的inode、fd等结构都有
也就是说这个file也能存在在我们进程的文件描述符数组里面,所以说,管道的创建思想是借用了文件创建的思想也就是复用内核的结构
父子进程之间的通信
我们知道,父子进程保持独立性的原因主要是一旦在代码中修改,就会发生写实拷贝,父子进程在物理内存的代码各一份,所以缺点很明显:数据只能提前搞好,一旦修改以后资源就不同
但管道通信还真是只能父子之间的,那是如何做到的呢?

所以父子进程就能共用一块资源做到通信,
如何代码实现通信呢?
那首先我们得知道,一个管道文件是被进程的两个fd指向的

那肯定要系统调用创建管道-pipe(int pipefd ),需要传一个两个元素的数组,其中pipe创建管道后 fd0对应读端,放到fd1对应写端,为什么要这样,不是说单向通信吗?因为你不知道是将来的子进程做输入还是做输出,并且将来你自己是做输入输出,所以创建的子进程会拷贝父进程,那它的文件描述符数组是和父进程一样的,它也有两个fd指向管道分别为读端和写端,将来谁要做写端,那就把另外一个进程的写端给close关闭调,然后自己的读端close掉,不就是单向通信么

下面是代码的小demo:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    int pipefd[2] = {0};
    int n = pipe(pipefd); // 创建管道
    if (n < 0)
    {
        perror("pipe");
    }
    printf("pipefd[0]: %d,pipefd[1]:%d\n", pipefd[0], pipefd[1]); // 0为读,1为写
    int id = fork();                                              // 创建子进程
    if (id == 0)
    {
        // child
        close(pipefd[0]);
        char outbuff[1024];
        int cnt = 5;
        char *msg = "hello pipe!";
        while (cnt)
        {
            snprintf(outbuff, sizeof(outbuff), "%s %d %d\n", msg, cnt--, getpid()); // snprintf会自动在末尾加\0
            write(pipefd[1], outbuff, strlen(outbuff));
            // close(pipefd[1]);
            // break;
            sleep(1);
        }
        printf("write pip over\n");
        exit(0);
    }
    close(pipefd[1]);
    char inbuff[1024];
    while (1)
    {
        // inbuff[0]=0;
        ssize_t n = read(pipefd[0], inbuff, sizeof(inbuff) - 1); // 减一是给\0留位置
        if (n > 0)
        {
            inbuff[n] = '\0';
            printf("%s", inbuff);
            close(pipefd[0]);
        }
        // printf("%s:%ld\n",inbuff,n);
        // sleep(1);
        else if (n == 0)
        {
            printf("read over");
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }
    //  close(pipefd[0]);
    int status = 0;
    int rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
        printf("%d,exit_code;%d,signal_code;%d\n",rid,WEXITSTATUS(status),status & 0x7f);
        
    }
    return 0;
}

下面是总结的匿名管道的特性与情况

特性 :
1、管道是单向通信的
2、匿名管道是具有"血缘关系"的进程,常用父子通信(继承内核资源)
3、管道随进程结束而结束(本质就是判断管道的进程引用引用计数)
4、管道是面向字节流的(也就是管道里的数据不一定是字符,图片、音频等都有可能)
字节流的说明:也就是你往管道里写多次数据,但输出的数据不一定是全部的数据,它是按需输出,就类似于语言级缓冲区数据刷新进内核级缓冲区的特性
5、管道通信对于多进程而言,是有同步和互斥的关系

同步:就是访问数据具有一定的顺序性,也就是访问数据需要排队(这只是简单的理解)
互斥:任何时刻只允许一个进程访问资源

情况(父进程为写端,子进程为读端):
1、父进程写的慢,子进程就要阻塞(管道为空,读就要阻塞),等管道有数据才读
2、父进程写的快,管道为满时,父进程要阻塞,子进程要读管道里的数据
3、读端在读,写端关闭,读端把管道数据读完后,read=0说明读到了管道文件末尾
4、读端关闭,OS就会把写端进程给杀掉(避免浪费资源)

三、命名管道

命名管道有自己的路径,跟普通文件不同的是它不做刷新内核缓冲区给磁盘,它的特性与匿名管道几乎差不多,除了两个进程通信是毫无相关的进程,无血缘关系的

实现命名管道的编写

创建命名管道

系统调用函数:mkfifo(char* path,mode)命令创建:mkfifo 文件名

c++创建命名管道:

框架如下:

cpp 复制代码
#define MODE 0666
#define TO_WRITE 1
#define TO_READ 2

class named_pipe
{
public:
    named_pipe(std::string pfile_path = "./fifo")
        : _pfile_path(pfile_path)
    {
    }
    ~named_pipe() {}
    private:
    std::string _pfile_path;
    pid_t _fd = -1;
};

创建管道文件的路径可以随意给我这里就是当前路径
1.创建管道

stat函数是判断你给的这个文件是否已经存在,struct stat结构体是会返回里面的属性,你需要可以通过这个结构体拿到

这里有个坑是不能用open函数用只读的方式打开文件看返回值的,因为对管道文件很特殊如果文件存在它会一直卡在这里,直到有进程以写的方式打开了管道文件才会往后走,所以如果这里用open会有bug

cpp 复制代码
 void Build_pipe()
    {
        if (IF_EXIST())
        {
            std::cout << "命名管道已存在,不需要再次创建!!!" << std::endl;
            return;
        }
        int n = mkfifo(_pfile_path.c_str(), MODE);
        if (n < 0)
        {
            std::cerr << "mkfifo errno:" << errno << strerror(errno) << std::endl;
        }
        std::cout << "命名管道创建成功!!!" << std::endl;
    }
 bool IF_EXIST()
    {
        // 能拿到文件的属性
        struct stat st;
        int n = stat(_pfile_path.c_str(), &st);
        if (!n)
            return true;
        else
            return false;
    }

2.让进程拿到不同的权限

cpp 复制代码
void Get_Power(int mode)
    {
        if (mode == TO_WRITE)
        {
            _fd = open(_pfile_path.c_str(), O_WRONLY);
            if(_fd < 0)
            {
                std::cout<< "open write end failure!!!" << std::endl;
            }
        }
        else if (mode == TO_READ)
        {
            _fd = open(_pfile_path.c_str(), O_RDONLY);
            if(_fd < 0) 
            std::cout<< "open read end failure!!!" << std::endl;
        }
        else
        {
            std::cout << "函数传参错误" << std::endl;
        }
        //下面是测试如果为管道文件,使用open打开文件时,不打开写端,open函数会一直堵塞
        if(_fd > 0)
        {
            std::cout << "open over" << std::endl;
        }
    }

3.删除管道文件

cpp 复制代码
void Delete_namedfile()
    {
        if(!(IF_EXIST()))
        return;
        int n = unlink(_pfile_path.c_str());
        std::cout << "删除管道文件成功!!!" << std::endl;
    }

后面就可以创建两个进程来看了,这里就展示进程server.cpp和client.cpp的代码来模拟客户端向服务端发送消息

cpp 复制代码
#include "named_pipe.hpp"

//server.cpp
int main()
{
    named_pipe np;
    np.Build_pipe();

    np.Get_Power(TO_READ);

    std::string msg;
    while(true)
    {
        char outbuff[128];
        ssize_t n = read(np.get_fd(),outbuff,sizeof(outbuff)-1);
        if(n > 0)
        {
            outbuff[n]= '\0';
            std::cout << outbuff << std::endl;
        }
        if(n==0)
        {
            //客户端关闭了,说明我也要结束了
            std::cout << "读取客户数据完毕!!!" << std::endl;
            break;
        }
        else
        {
            std::cerr << "read erro" << std::endl;
        }
    }
    np.Delete_namedfile();
    return 0;
}
cpp 复制代码
#include "named_pipe.hpp"

//client.cpp
int main()
{
    named_pipe np;
    np.Get_Power(TO_WRITE);

    while(true)
    {
        std::string msg;
        getline(std::cin,msg);
        ssize_t n = write(np.get_fd(),msg.c_str(),msg.size());
        if(n > 0)
        {
            std::cout << "当前客户端向服务端输入" << n << "个字节" << std::endl;
        }
        
    }
    return 0;
}
相关推荐
w01_02_031 小时前
cpu , 控制 ,计算。
linux
醇氧1 小时前
uvicorn 详细介绍
linux·运维·python·python3.11
落魄实习生1 小时前
Agent Scope Java 2.x 系列【11】AgentState 与状态存储
java·开发语言·ai
Ivanqhz1 小时前
干涉图着色
java·服务器·网络·数据库·人工智能·深度学习
xiaoye-duck2 小时前
《Linux 网络编程》深入理解 IO 多路复用:select服务器完善、poll 接口详解与服务端改造实战
linux·网络
wacsii2 小时前
【Linux基础开发工具(二)】Vim编辑器从入门到实战:模式、命令、配置与常见问题全解析
linux
꯭自꯭闭꯭2 小时前
达梦DMDSC主备搭建
linux·服务器·数据库
用户094248568032 小时前
第20章:JVM G1 GC原理、日志与调优实战
java·jvm
打工仔折腾 AI2 小时前
把模型切换交给平台:用蓝耘智能路由搭建商品评论分析工具
java·服务器·前端·后端·python·性能优化·ai agent 实战