进程间通信 ---- 基于管道来实现

目录

进程间通信的定义

进程间通信的目的

进程间通信的发展

进程间通信的分类

进程间通信的方式

管道

匿名管道

匿名管道的创建

管道实现父子进程通信的原理

管道读写规则

匿名管道的特点

命名管道

命名管道的创建和删除

命名管道的打开规则


进程间通信的定义

进程间通信(Inter-Process Communication ---- IPC)的定义:不同进程之间交换数据、传递信息、协同工作的机制。

为什么要有IPC?

因为每个进程具有独立性,进程之间是相互隔离的。

进程间通信的目的

数据传输:一个进程需要将它的数据发送个另一个进程。(一个进程生成数据交给另一个进程来处理数据)

资源共享:多个进程之间共享同样的资源。(多个进程通过共享内存,同时读取同一份配置数据,避免数据拷贝)

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件,使其执行某种回应。(子进程退出时,通过信号通知父进程,让父进程回收资源)

进程控制:有些进程希望完全控制另一个进程的执行(例如调试进程),来监视它的运行过程。

进程间通信的发展

管道 IPC -> System V IPC -> POSIX IPC

进程间通信的分类

管道 IPC:匿名管道 ---- 命名管道

System V IPC:System V ---- 消息队列 ---- System V 共享内存 ---- System V 信号量

POSIX IPC:消息队列 ---- 共享内存 ---- 信号量 ---- 互斥量 ---- 条件变量 ---- 读写锁

进程间通信的方式

单工:单向固定传输,只能一方发、一方收,无法反向。(例如信号)

半双工:双方均可收发,同一时刻只能单向传输。(例如管道)

全双工:双方同时收发,双向并行传输。(例如Socket)

管道

管道的定义:内核级维护的一块环形缓冲区,用来进行数据传输。

通信方式:半双工。

匿名管道

匿名管道的定义:Linux内核维护的无名环形缓冲区,属于半双工通信,仅用来具有血缘关系(常用于父子进程)的进程进行进程间通信,无磁盘文件实体,进程结束管道自动销毁。

匿名管道的创建

pipe -> 创建一个匿名管道

int pipe(int pipefd[2]);

pipefd[0]:表示读端 pipe[1]:表示写端

成功:返回 0 失败:返回 -1

管道实现父子进程通信的原理

fork之前

fork之后

父子进程间单向管道通信的最终状态 ---- 关闭多余管道端(父写子读或父读子写)

代码演示

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

void Read(int rfd)
{
    // 子进程读取数据
    while (1)
    {
        char buf[1024];
        int n = read(rfd, buf, sizeof(buf) - 1);
        if (n > 0)
        {
            buf[n] = 0;
            printf("父进程说:%s\n", buf);
        }
        else if (n == 0)
        {
            printf("子进程正常退出\n");
            break;
        }
        else
        {
            printf("子进程异常读取");
            break;
        }
    }
}

void Write(int wfd)
{
    // 父进程写入数据
    int cnt = 5;
    int i = 0;
    char* buf[5] = {"hello world", "hello linux", "wait", "quit", "haha"};
    while (cnt--)
    {
        int n = write(wfd, buf[i], strlen(buf[i]));
        ++i;
        sleep(1);
    }
}

int main()
{
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    if (n < 0)
    {
        perror("make pipe error");
        exit(1);
    }
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if (id == 0)
    {
        close(pipefd[1]);
        Read(pipefd[0]);
        close(pipefd[0]);
        exit(0);
    }
    close(pipefd[0]);
    Write(pipefd[1]);
    close(pipefd[1]);
    waitpid(id, NULL, 0);
    return 0;
}

管道读写规则

  1. 读端快,写端慢 ---- 读端进程阻塞在read调用内部,知道写端进行写入数据

  2. 读端慢,写端快 ---- 管道是有容量大小的,管道被写满时,写端进程阻塞在write调用内部,等待读端进行读取,由于管道是环状结构,一旦读端把数据读取,则该数据所占用的空间可以被读端进行覆盖写入

  3. 读端关闭,写端没关 ---- 写段进程进行write调用内部会产生信号SIGPIPE,从而被操作系统终止

  4. 读端没关,写段关闭 ---- 读端进程会读到管道有效数据的末尾,再次调用read则返回0

匿名管道的特点

1.只能用于具有血缘关系的进程进行进程间通信。通常的做法:一个匿名管道由一个进程创建,然后该进程fork,此后父、子进程之间通过匿名管道进行通信。

  1. 管道的生命周期随着进程(原因:读端和写端的所有文件描述符都被关闭,管道释放。)

  2. 内核会对管道操作进行同步与互斥

  3. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立两个管道。

  4. 管道是面向字节流的

命名管道

命名管道(FIFO)的定义:一种以文件形式存在于文件系统中的特殊文件(文件类型为 p),支持任意进程(有无血缘关系的进程)进行进程间通信,属于半双工通信。

本质特性:磁盘上存在,内核会为其维护环形缓冲区,但缓冲区里面的内容不会刷新到磁盘上。

命名管道的特点、读写规则、原理与匿名管道基本一致。

命名管道的创建和删除

命令行创建命名管道

mkfifo fifo

prw-rw-r-- 1 lt lt 0 May 23 21:43 fifo|

命令行删除命名管道

rm fifo

unlink fifo

可执行程序创建命名管道

int mkfifo(const char *filename,mode_t mode);

可执行程序删除命名管道

int unlink(const char *pathname);

命名管道的打开规则

以读的方式打开:打开之前没有其他进程以写的方式打开 -> 阻塞到open调用内部直到有其他进程以写的方式打开

以写的方式打开:打开之前没有其他进程以读的方式打开 -> 阻塞到open调用内部直到有其他进程以读的方式打开

那么谁先阻塞结束呢?

核心结论:后到达匹配条件的一方先结束阻塞

  1. 读端先 open 阻塞,后续写端执行 open,写端先成功返回,然后读端阻塞解除返回

  2. 写端先 open 阻塞,后续读端执行 open,读端先成功返回,然后写端阻塞解除返回

举例:

进程 A 只读打开 FIFO→阻塞

进程 B 只写打开 FIFO→B 先结束阻塞返回,A 同步唤醒结束阻塞

代码实现 ---- 用命名管道实现server 和 client 进程间通信

server.cc

cpp 复制代码
#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    umask(0);
    // 创建命名管道
    int n = mkfifo("fifo", 0664);
    if(n < 0)
    {
        perror("make fifo error");
        exit(1);
    }

    // 读数据
    int fd = open("fifo", O_RDONLY);

    while(true)
    {
        char buf[1024];
        int n = read(fd, buf, sizeof(buf) - 1);
        if(n > 0)
        {
            buf[n] = 0;
            std::cout << "client say:" << buf << std::endl;
        }
        else if(n == 0)
        {
            std::cout << "client 已退出,并且数据已读取完毕" << std::endl;
            break;
        }
        else
        {
            std::cout << "读取异常" << std::endl;
            break;
        }
    }

    close(fd);
    unlink("fifo");
    return 0;
}

client.cc

cpp 复制代码
#include <iostream>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{

    // 写数据
    int fd = open("fifo", O_WRONLY);

    while(true)
    {
        std::cout << "Prease Input #:";
        std::string buf;
        std::getline(std::cin, buf);

        write(fd, buf.c_str(), buf.size());
    }

    close(fd);

    return 0;
}

代码运行结果

相关推荐
kebidaixu2 小时前
BCU项目CMake 构建管理
linux
Yunzenn2 小时前
深度解析字节前沿研究-Cola DLM第 04 章:Cola DLM 架构全景 —— 三层解耦的设计哲学
java·linux·python·深度学习·面试·github·transformer
皆圥忈2 小时前
Linux 进程从入门到实战(三)
linux
Bert.Cai2 小时前
Linux sort命令详解
linux·运维·服务器
开开心心就好2 小时前
免费无广告的批量卸载与系统清理工具
linux·服务器·网络·智能手机·rabbitmq·excel·memcached
倔强的石头1062 小时前
SenseNova-U1 实战体验:从网页版生成,到 Mac 踩坑,再到 CUDA 服务器跑通本地部署
运维·服务器·macos
l16775168542 小时前
天翼云服务器失联排查完整报告_事件报告
运维·服务器·云原生·云计算
wanhengidc2 小时前
高防服务器中的数据安全
运维·服务器·网络
艾莉丝努力练剑2 小时前
【Linux网络】Linux 网络编程:HTTP(五)HTTP收尾,从Cookie会话保持、抓包问题到 HTTPS 初识
linux·运维·服务器·网络·c++