【Linux】进程间通信方式之管道

🤖个人主页晚风相伴-CSDN博客

💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧

🙏如果内容有误的话,还望指出,谢谢!!!

✨下一篇文章:《进程间通信之共享内存》敬请期待💪

目录

理解进程间通信的本质

管道

管道的分类

匿名管道

匿名管道如何实现进程间通信

从文件描述符的角度理解匿名管道

用代码实现匿名管道

匿名管道读写的4个特殊情况

管道的特点

利用匿名管道设计一个进程池

命名管道

命名管道如何实现进程间通信

命名管道的创建

代码实现命名管道


理解进程间通信的本质

因为进程具有独立性,所以每个进程都只知道自己,而不知道有另外的进程存在,所以要实现不同进程间的通信,就要让不同的进程都能看到同一块资源,这块资源不属于任意一个进程,而是强调共享,利用这块资源就可以实现进程间通信了。

总结一下要点

  1. 进程间通信的前提是要让不同的进程看到同一块资源
  2. 这一块资源不隶属于任何一个进程,而是被这些进程所共享

管道

管道想必大家都不陌生吧,在Linux命令行中我们可以通过管道( | )将一个进程输出连接到另一个进程的输入,从而实现数据的传输、连接、过滤和处理等功能。例如

管道也好理解就比如家里面的水管,一端进水,另一端出水。

管道的分类

  • 匿名管道
  • 命名管道

🔥匿名管道

匿名管道主要用于父子进程之间的通信。

创建匿名管道的接口

参数:fildes是一个文件描述符数组,fildes[0]表示读端,fildes[1]表示写端

返回值:成功返回0,失败则返回-1并设置对应的错误码

🔥 匿名管道如何实现进程间通信

  1. 父进程创建匿名管道,得到两个文件描述符,一个文件描述符用来读数据,另一个文件描述符用来写数据
  2. 调用fork创建子进程,创建出来的子进程会继承父进程创建管道时获得的那两个文件描述符
  3. 在父子进程中关闭不需要的文件描述符,比如子进程读取数据,父进程写入数据,那就在子进程中关闭写入的文件描述符,父进程中关闭读取的文件描述符,之后就可以进行相应的通信操作了。

🔥从文件描述符的角度理解匿名管道

用代码实现匿名管道

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main()
{
    //1.创建管道
    int pipefd[2] = {0};//pipefd[0]:读端,pipefd[1]:写端
    int n = pipe(pipefd);
    assert(n != -1);//在Debug下才有用
    (void)n;//确保在release不报警告

//条件编译
#ifdef DEBUG
    cout << "pipefd[0]:" << pipefd[0] << endl;
    cout << "pipefd[1]:" << pipefd[1] << endl;
#endif

    //2.创建子进程并构建通信信道------父进程写入,子进程读取
    pid_t id = fork();
    assert(id != -1);
    if(id == 0)
    {
        //子进程
        close(pipefd[1]);//关闭子进程不需要的fd

        //读取数据
        char buffer[1024];
        while(true)
        {
            //sleep(10);
            ssize_t s = read(pipefd[0], buffer, sizeof buffer - 1);
            if(s > 0)
            {
                buffer[s] = 0;
                cout << "child get a massage [" << getpid() << "] father say:" << buffer << endl;
            }
        }

        close(pipefd[0]);
        exit(0);
    }
    //父进程
    close(pipefd[0]);//关闭父进程不需要的fd

    //发送数据
    string message = "我是父进程,我正在给你发送数据";
    int count = 0;//统计发送次数
    char send_buffer[1024];
    while(true)
    {
        //sleep(10);
        //将要发送的数据打入send_massage中
        snprintf(send_buffer, sizeof send_buffer, "%s[%d] : %d\n", message.c_str(), getpid(), count++);
        //写入数据
        write(pipefd[1], send_buffer, strlen(send_buffer));
        sleep(1);
    }

    //等待子进程退出
    pid_t ret = waitpid(id, nullptr, 0);
    assert(ret);
    (void)ret;

    close(pipefd[1]);
    return 0;
}

运行结果

🔥匿名管道读写的4个特殊情况

情况一:写端很快,读端很慢,写端将缓冲区写满后就不能再写了,必须等读端读取后才能写让子进程读端sleep(10)秒钟

情况二:写端很慢,读端很快,管道中没有数据的时候,读端必须等待写端写入数据之后才能读

让父进程写端sleep(10)秒钟

情况三:写端关闭,读端读到0时表示读到了文件末尾

现象就是它会阻塞在那

情况四:读端关闭,写端继续写,当将缓冲区写满后操作系统会终止写进程

🔥管道的特点

  1. 管道是用于具有亲缘关系的进程之间进行进程间通信(常用于父子进程)
  2. 管道可以让父子进程之间进行协同,并且提供了访问控制(上面的4种情况就是访问控制的体现)
  3. 管道提供的是面向流式的通信服务(面向字节流)
  4. 管道的生命周期是随进程的
  5. 管道是单向通信的,也就是半双工通信的一种特殊情况

完整代码链接: 匿名管道

利用匿名管道设计一个进程池

原理:fork多个子进程,然后用个随机数式的负载均衡让多个子进程都有概率能被使用到

匿名管道版进程池完整代码链接: 匿名管道版进程池

🔥命名管道

匿名管道的一个限制就是只能在具有亲缘关系的进程间通信,但是如果我们想在不相关的进程之间实现通信,那么就需要用到命名管道。

🔥命名管道如何实现进程间通信

要实现进程间通信本质是要让不同的进程看到同一块资源。命名管道其实是在磁盘上创建了一个管道文件,这个管道文件可以随意被命名,并且有对应的属性但是没有内容,当我们在一个进程中打开这个管道文件并且将数据写入这个文件中,另一个进程也就可以打开这个管道文件并且从这个文件中读取数据,不同的进程都能打开并使用这个管道文件,所以也就让不同的进程看到了同一块资源。并且在这读写过程中管道文件中的数据不会加载到磁盘中,所以管道文件的大小始终为0保持不变。

命名管道的创建

在命令行上可以使用mkfifo命令来创建一个命名管道

也可以使用系统接口来创建命名管道

参数

  • pathname :命名管道文件的路径
  • mode:管道文件的权限

返回值:成功返回0,失败则返回-1

代码实现命名管道

comm.hpp

cpp 复制代码
#ifndef _COMM_H_
#define _COMM_H_

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"

using namespace std;

#define MODE 0666 // 文件权限
#define SIZE 128

string ipcPath = "./myfifo.ipc"; // 管道文件路径

#endif

server.cc

cpp 复制代码
#include "comm.hpp"
#include <sys/wait.h>

static void getMessage(int fd)
{
    // 进行通信操作
    char buffer[SIZE];
    while (true)
    {
        memset(buffer, '\0', sizeof buffer);
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            // 读取成功
            cout << "[" << getpid() << "] "
                 << "client say> " << buffer << endl;
        }
        else if (s == 0)
        {
            // 读到文件结尾
            cerr << "[" << getpid() << "] "
                 << "read end of file, client quit, server quit too!" << endl;
            break;
        }
        else
        {
            // 读取失败
            perror("read");
            break;
        }
    }
}

int main()
{
    // 创建管道文件
    if (mkfifo(ipcPath.c_str(), MODE) < 0)
    {
        perror("mkfifo");
        exit(1);
    }
    logMessage(NORMAL, "创建管道文件成功, step 1"); // 打印日志

    // 打开管道文件
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(2);
    }
    logMessage(NORMAL, "打开管道文件成功, step 2");

    int nums = 5;
    for (int i = 0; i < nums; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            // 子进程抢占式读取消息
            getMessage(fd);
            exit(1);
        }
    }

    // 等待子进程退出
    for (int i = 0; i < nums; i++)
    {
        waitpid(-1, nullptr, 0);
    }

    // 关闭文件
    close(fd);
    logMessage(NORMAL, "关闭管道文件成功, step 3");

    // 通信完成,将管道文件删除
    unlink(ipcPath.c_str());
    logMessage(NORMAL, "删除管道文件成功, step 4");
    return 0;
}

client.cc

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

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

    //发送消息
    string buffer;
    while(true)
    {
        cout << "Please Enter Message Line > ";
        getline(cin, buffer);
        write(fd, buffer.c_str(), buffer.size());
    }

    //关闭文件描述符
    close(fd);
    return 0;
}

完整代码链接命名管道

相关推荐
limengshi13839219 分钟前
通信工程学习:什么是TFTP简单文件传输协议
网络·网络协议·学习·信息与通信
powerfulzyh44 分钟前
Ubuntu24.04远程开机
linux·ubuntu·远程工作
ulimpid44 分钟前
Command | Ubuntu 个别实用命令记录(新建用户、查看网速等)
linux·ubuntu·command
HHoao1 小时前
Ubuntu启动后第一次需要很久才能启动GTK应用问题
linux·运维·ubuntu
小灰兔的小白兔1 小时前
【Ubuntu】Ubuntu常用命令
linux·运维·ubuntu
GFCGUO1 小时前
ubuntu18.04运行OpenPCDet出现的问题
linux·python·学习·ubuntu·conda·pip
winds~1 小时前
ubuntu中软件的进程管理-结束软件运行
linux·运维·ubuntu
阳光不锈@1 小时前
麒麟桌面系统安装和配置Node.js
linux·麒麟系统安装node.js
bush41 小时前
使用root账号ssh登录虚拟机ubuntu
运维·ubuntu·ssh
叫我龙翔2 小时前
【Linux】进程间关系与守护进程
linux·运维·服务器·计算机网络