Linux基础内容(19)—— 进程间通信(介绍与管道内容)

Linux基础内容(18)—— 动静态库_哈里沃克的博客-CSDN博客icon-default.png?t=N2N8https://blog.csdn.net/m0_63488627/article/details/129927034?spm=1001.2014.3001.5501

目录

1.简单进程介绍

1.概念前提

2.通信定义

3.通信的意义

2.进程间通信的方案

1.管道

1.匿名管道

2.命名管道


1.简单进程介绍

1.概念前提

1.进程之间由于虚拟地址空间的设计,使得不同进程之间相互独立

2.通信就是将进程之间能够相互关联

2.通信定义

1.数据传输:数据由一个进程传给另一个进程

2.资源共享:进程之间有共享的资源

3.通知事件:进程能够通知另一个进程反映的信息

4.进程控制:一个进程控制另一个进程

3.通信的意义

因为有些时候需要多进程协同

本质:

1.OS要给进程提供“公共资源”

2.进程通信需要一份都能看到的“公共资源”

2.进程间通信的方案

两套常用标准:

方案1 POSIX  -- 让通信过程可以跨主机

方案2 Systen V -- 聚焦本地通信

1.管道

1.管道基于文件系统,并非上面的两套标准之一

2.管道分为:命名管道和匿名管道

3.一个线程连接另一个进程的一个数据流称为“管道”

1.匿名管道

管道的由来

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第1张图片

1.对于子进程而言,一切拷贝自父进程,所以该进程的文件描述符表也一样拷贝,不过在这一层而言,都是独立的,因为系统的虚拟内存空间的设计

2.在内核中,父子进程的文件描述符表由于复制固然相等,以至于,他们指向内存的文件是一样的,所以在这一层数据是共享的

3.打破了数据独立,由于内核中有了公共部分,那么就能设计出完成通信的方法

4.不必需要刷新到磁盘后再提取内容,其实只要一个进程往另一个进程输入即可

5.因为不用磁盘,那么其实我们不需要特地创造一个文件专门来通信,由此管道是一种内存级文件,不需要磁盘的操作

6.内存级文件无法在磁盘路径中找到,那么我们也无法知道其名字,那么我们称这个管道叫匿名管道

真正的管道原理如图:

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第2张图片

 步骤分析:

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第3张图片

父进程创建管道,分别以读写方式打开;如果只打开一端,那么子进程创建会出现跟父进程一样的操作,但是因为管道匿名,则不能再改变了

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第4张图片

父进程fork()出子进程,子进程与父进程同样拥有读写操作

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第5张图片

父进程去掉读端,子进程去掉写端 / 父进程去掉写端,子进程去掉读端

由图可知一般而言,管道只能单项数据通信。

创造一个pipe的代码Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第6张图片

 [0] -- 读取,[1] -- 写入

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int main()
{
    // 第一步:创建管道文件,打开读写端
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);

    // 0,1,2 -> 3,4
    // cout << "fds[0]: " << fds[0] << endl;   读
    // cout << "fds[1]: " << fds[1] << endl;   写

    // 第二步:fork
    pid_t id = fork();
    assert(id >= 0);
    if (id == 0)
    {
        // 子进程写,关闭读端
        close(fds[0]);
        // 子进程的通信代码
        const char *s = "我是子进程,我正在给你发消息";
        int cnt = 0;
        while (true)
        {
            cnt++;
            char buffer[1024]; // 只有子进程能看到buffer的内容
            snprintf(buffer, sizeof buffer, "child->parent say: %s[%d][%d]", s, cnt, getpid());
            write(fds[1], buffer, strlen(buffer)); // 输入到文件里的,不需要考虑+1,因为文件不管"\0"
            sleep(1);
        }
        // 子进程
        exit(0);
    }
    // 父进程读,关闭写端
    close(fds[1]);
    // 父进程的通信代码
    while (true)
    {
        char buffer[1024];                                    // 只有父进程能看到buffer的内容
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1); // 文件中拿出来,需要空出一位
        if (s > 0)
            buffer[s] = 0; // 最后一位变\0
        cout << "Get Massage# " << buffer << " | my pid: " << getpid() << endl;
        // 父进程可以不用sleep
    }
    // 父进程
    int m = waitpid(id, nullptr, 0);
    assert(m == id);

    return 0;
}

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第7张图片

原理图

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第8张图片 两个进程是独立的,但是操作系统提供了共同访问的公共资源,以让进程通过调用系统函数从而完成操作。管道中读取信息,之后管道里该段信息会被无效化,不会再次呈现。

1.在读取时,写端暂停写入,读端会读完要求的数据后阻塞等待

2.读端阻塞,写端会写满要求的,随后也阻塞

3.关闭写端,读端接收到0证明读到末尾,之后的操作读段可以选择断开

4.关闭读段,写端会被操作系统传递信号终止,信号为13号,可以通过waitpid调用信号

管道的特征:

1.管道的生命周期随进程

2.管道可在有血缘关系的进程里通信

3.管道面向字节流,字节流就是不管管道输入的是什么类型的信息

4.半双工 -- 单向通信

5.互斥与同步 -- 用于保护公共资源的一种方案

Linux的管道" | "运行机理

bush会创建两个进程,随后由于血缘关系进程可以通信

2.命名管道

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第9张图片

 Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第10张图片

p为管道文件,有自己的inode,说明是一个独立文件

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第11张图片

 两个非血缘关系的文件打开同一份文件,得到同一块公共资源在内核中,而命名管道因为其有自己的名字,通过路径和名字,可以标定唯一的文件,也就是说进程可以找到同一个资源。

 1.创建函数的调用

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第12张图片

管道文件名和文件的权限

bool creatFifo(const std::string &path)
{
    umask(0);
    int n = mkfifo(path.c_str(), 0600);
    if (n == 0)
        return true;
    else
    {
        std::cout << "error: " << errno << " err string: " << strerror(errno) << std::endl;
        return false;
    }
}

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第13张图片

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第14张图片

2.销毁管道文件

Linux基础内容(19)—— 进程间通信(介绍与管道内容)_第15张图片

void removeFifo(const std::string &path)
{
    int n = unlink(path.c_str());
    assert(n);
    (void)n;
}

 一个进程创建管道文件,会阻塞在创建管道文件这里;需要另一个进程连接管道文件完毕才让阻塞的进程进一步执行。

//comm.hpp
#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define NAMED_PIPE "/tmp/mypipe"

bool creatFifo(const std::string &path)
{
    umask(0);
    int n = mkfifo(path.c_str(), 0600);
    if (n == 0)
        return true;
    else
    {
        std::cout << "errno: " << errno << " err string: " << strerror(errno) << std::endl;
        return false;
    }
}

void removeFifo(const std::string &path)
{
    int n = unlink(path.c_str());
    assert(n);
    (void)n;
}

//client.cc
#include "comm.hpp"

int main()
{
    std::cout << "client begin" << std::endl;
    int wfd = open(NAMED_PIPE, O_WRONLY);
    std::cout << "client end" << std::endl;
    if (wfd < 0)
        exit(1);

    // write
    char buffer[1024];
    while (true)
    {
        std::cout << "Please Say# ";
        fgets(buffer, sizeof(buffer), stdin);
        if (strlen(buffer) > 0)
            buffer[strlen(buffer) - 1] = 0;  //消掉在shell上输入时的换行
        ssize_t n = write(wfd, buffer, strlen(buffer));
        assert(n == strlen(buffer));
        (void)n;
    }

    close(wfd);
    return 0;
}

//server.cc
#include "comm.hpp"

int main()
{
    bool r = creatFifo(NAMED_PIPE);
    assert(r);
    (void)r;

    std::cout << "server begin" << std::endl; // 打开即打印
    int rfd = open(NAMED_PIPE, O_RDONLY);     // 打开后检查client,没有打开则阻塞
    std::cout << "server begin" << std::endl; // client准备好再打印这个
    if (rfd < 0)
        exit(1);

    // read
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "clien->serve# " << buffer << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "clien quit,me too!" << std::endl;
            break;
        }
        else
        {
            std::cout << "err string: " << strerror(errno) << std::endl;
            break;
        }
    }
    close(rfd);

    removeFifo(NAMED_PIPE);
    return 0;
}

你可能感兴趣的:(linux,服务器,c++,centos,c语言)