[Linux]进程间通信--管道

这篇具有很好参考价值的文章主要介绍了[Linux]进程间通信--管道。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

[Linux]进程间通信–管道

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程 。
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

实现进程间通信的原理

进程是具有独立性的,一个进程是无法看到另一个进程的代码和数据的,为了让进程间通信,要做的工作就是让不同的进程看到同一份“资源”。

任何进程通信手段需要解决的问题如下:

  • 让不同的进程看到同一份“资源”
  • 让一方进行读取,另一方进行写入

不同的进程间通信手段本质的区别就是让不同的进程看到同一份“资源”的方式不同。

匿名管道

匿名管道是一种以文件为媒介的通信方式,匿名管道是一个内存级别的文件,拥有和普通文件一样的缓冲区,但是操作系统不会将缓冲区刷新至外设,匿名管道虽然是文件,但是由于没有文件路径,进程是无法通过系统文件接口来操作的,因此匿名管道通常用于父子进程之间使用。

匿名管道的通信原理

由于匿名管道没有文件路径,进程是无法通过系统文件接口来操作的特性,匿名管道必须通过父进程创建,子进程继承父进程文件描述符表的方式,使得不同的进程看到同一个文件:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

由于匿名管道只支持单向通信,在使用匿名管道进行通信时,父进程必须分别以读方式和写方式打开管道文件,子进程继承了文件描述符表后,一方关闭读端,一方关闭写端进行通信。

注意: 如果父进程只以读方式或者写方式打开,子进程继承文件描述符表后,也是同样的方式,子进程自身无法打开该管道,因此导致无法通信。

系统接口

Linux系统提供了创建匿名管道的系统接口pipe:

//pipe所在的头文件和声明
#include <unistd.h>

int pipe(int pipefd[2]);
  • pipefd为输出型参数,用于接收以读方式和写方式打开管道的文件描述符。
  • pipefd[0]获取读端文件描述符,pipefd[1]获取写端文件描述符。
  • 成功返回0,失败返回-1,错误码被设置。

编写如下代码测试pipe接口:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>

using namespace std;

int main()
{
    //创建管道
    int pipefd[2] = { 0 };
    int n = pipe(pipefd);
    if (n < 0)//出错判断
    {
        cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
        exit(1); 
    }
    //创建子进程
    pid_t id = fork();
    assert(id != -1);//出错判断

    //进行通信 -- 父进程进行读取,子进程进行写入
    if (id == 0)
    {
        //子进程
        close(pipefd[0]);
        
        const string str = "hello world";
        int cnt = 1;
        char buffer[1024];
        while(1)
        {
            snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);
            write(pipefd[1], buffer, strlen(buffer));//向管道写入数据
            sleep(1);
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    close(pipefd[1]);
    char buffer[1024];
    while(1)
    {
        read(pipefd[0], buffer, sizeof(buffer) - 1);//从管道读取数据
        cout << "我是父进程," << "child give me: " << buffer << endl;
    }
    close(pipefd[0]);
    return 0;
}

编译代码运行查看结果:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

从运行结果可以看出,建立管道后,父子进程就能够进行数据通信。

管道特性

  1. 单向通信,半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
  2. 管道的本质是文件,因此管道的生命周期随进程
  3. 管道通信,通常适用于具有“血缘关系的进程”,诸如父子进程、兄弟进程等
  4. 管道的数据是以字节流的形式传输的,读写次数的多数不是强相关的
  5. 具有一定的协同机制

管道的协同场景

场景一: 如果管道内部的数据被读端读取完了,写端不写入,读端就只能等待

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>

using namespace std;

int main()
{
    //创建管道
    int pipefd[2] = { 0 };
    int n = pipe(pipefd);
    if (n < 0)
    {
        cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
        exit(1); 
    }
    //创建子进程
    pid_t id = fork();
    assert(id != -1);

    //进行通信 -- 父进程进行读取,子进程进行写入
    if (id == 0)
    {
        //子进程
        close(pipefd[0]);
        
        const string str = "hello world";
        int cnt = 1;
        char buffer[1024];
        while(1)
        {
            snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);
            write(pipefd[1], buffer, strlen(buffer));
            sleep(100); // ---------  模拟写入暂停  --------- 
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    close(pipefd[1]);
    char buffer[1024];
    while(1)
    {
        read(pipefd[0], buffer, sizeof(buffer) - 1);
        cout << "我是父进程," << "child give me: " << buffer << endl;
    }
    close(pipefd[0]);
    return 0;
}

编译代码运行查看结果:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

场景二: 如果管道内部的数据被写端写满了,读端不读取,写端无法继续写入

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>

using namespace std;

int main()
{
    //创建管道
    int pipefd[2] = { 0 };
    int n = pipe(pipefd);
    if (n < 0)
    {
        cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
        exit(1); 
    }
    //创建子进程
    pid_t id = fork();
    assert(id != -1);

    //进行通信 -- 父进程进行读取,子进程进行写入
    if (id == 0)
    {
        //子进程
        close(pipefd[0]);
        
        const string str = "hello world";
        int cnt = 1;
        char buffer[1024];
        while(1)
        {
            snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);
            write(pipefd[1], buffer, strlen(buffer));
            printf("cnt: %d\n", cnt); // ---------  显示写入过程  --------- 
            //sleep(100);
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    close(pipefd[1]);
    char buffer[1024];
    while(1)
    {
        sleep(100); // ---------  模拟读取暂停  --------- 
        read(pipefd[0], buffer, sizeof(buffer) - 1);
        cout << "我是父进程," << "child give me: " << buffer << endl;
    }
    close(pipefd[0]);
    return 0;
}

编译代码运行查看结果:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

场景三: 写端关闭,读端读完了管道内部的数据时,再读就读到了文件的结尾。

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>

using namespace std;

int main()
{
    //创建管道
    int pipefd[2] = { 0 };
    int n = pipe(pipefd);
    if (n < 0)
    {
        cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
        exit(1); 
    }
    //创建子进程
    pid_t id = fork();
    assert(id != -1);

    //进行通信 -- 父进程进行读取,子进程进行写入
    if (id == 0)
    {
        //子进程
        close(pipefd[0]);
        
        const string str = "hello world";
        int cnt = 1;
        char buffer[1024];
        while(1)
        {
            snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);
            write(pipefd[1], buffer, strlen(buffer));
            printf("cnt: %d\n", cnt);
            sleep(1);
            if (cnt == 5) break; // ---------  写端关闭  --------- 
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    close(pipefd[1]);
    char buffer[1024];
    while(1)
    {
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            cout << "我是父进程," << "child give me: " << buffer << endl;
        }
        else if (n == 0)// ---------  判断读取到文件末尾  --------- 
        {
            cout << "读取完毕, 读到文件结尾" << endl;
            break;
        }
        else
        {
            cout << "读取出错" << endl;
            break;
        }
    }
    close(pipefd[0]);
    return 0;
}

编译代码运行查看结果:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

**场景四:**写端一直写,读端关闭,操作系统会给写端发送13号信号终止进程。

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

using namespace std;

int main()
{
    //创建管道
    int pipefd[2] = { 0 };
    int n = pipe(pipefd);
    if (n < 0)
    {
        cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;
        exit(1); 
    }
    //创建子进程
    pid_t id = fork();
    assert(id != -1);

    //进行通信 -- 父进程进行读取,子进程进行写入
    if (id == 0)
    {
        //子进程
        close(pipefd[0]);
        
        const string str = "hello world";
        int cnt = 1;
        char buffer[1024];
        while(1)
        {
            snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);
            write(pipefd[1], buffer, strlen(buffer));
            printf("cnt: %d\n", cnt);
            sleep(1);
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    close(pipefd[1]);
    char buffer[1024];
    while(1)
    {
        int cnt = 0;
        //sleep(100);
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            cout << "我是父进程," << "child give me: " << buffer << endl;
        }
        else if (n == 0)
        {
            cout << "读取完毕, 读到文件结尾" << endl;
            break;
        }
        else
        {
            cout << "读取出错" << endl;
            break;
        }
        //sleep(100);
        sleep(5);
        break;// ---------  读端关闭  --------- 
    }
    close(pipefd[0]);
    int status = 0;
    waitpid(id, &status, 0);
    cout << "signal: " << (status & 0x7F) << endl;// --------- 回收子进程获取退出信号  --------- 
    sleep(3);
    return 0;
}

编译代码运行查看结果:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

管道的大小

在Linux下,管道(Pipe)的大小受到操作系统的限制。具体来说,管道的大小由内核参数PIPE_BUF定义,通常是4096个字节。

  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

命名管道

命名管道同样是内存级的文件,和匿名管道的区别就是命名管道可以在指定路径下创建,并且命名可以指定,因此命名管道可以给任何两个不同的进程用于通信。

使用指令创建命名管道

Linux下使用mkfifo 指令就可以在指定路径下创建命名管道。

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

命名管道同样和匿名管道一样满足管道的协同场景:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

写端尝试打开管道文件,没有读端,写端就会卡在打开文件这一步骤。

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

右侧读端开始会等待写端写入,后续关闭右侧读端,左侧写端进程直接被终止。

使用系统调用创建命名管道

//mkfifo所在的头文件和声明
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
  • pathname参数 – 创建命名管道的路径
  • mode参数 – 创建命名管道的文件权限
  • 成功返回0,失败返回-1,错误码被设置。

为了测试mkfifo接口编写代码进行测试,首先设置文件结构如下:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言

makefile文件内容如下:

.PHONY:all
all:client server

client:client.cc
	g++ -o $@ $^ -std=c++11

server:server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf client server

common.hpp主要用于让两个进程获取管道路径,具体内容如下:

#include <iostream>
#include <string>

#define NUM 1024

const std::string pipename = "./namepipe"; //管道的路径和管道名

mode_t mode = 0666; //创建管道的文件权限

client.cc作为写端输入数据,具体内容如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <cassert>
#include "commn.hpp"

int main()
{
    // 打开管道文件
    int wfd = open(pipename.c_str(), O_WRONLY);
    if (wfd < 0)
    {
        std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
        exit(1);
    }

    //进行通信
    while(true)
    {
        char buffer[NUM];
        std::cout << "请输入内容:";
        fgets(buffer, sizeof(buffer), stdin);//获取用户输入
        buffer[strlen(buffer) - 1] = 0;

        if (strcasecmp(buffer, "quit") == 0) break;//用户输入quit退出进程

        ssize_t size = write(wfd, buffer, strlen(buffer));
        assert(size >= 0);
        (void)size;
    }

    close(wfd);
    return 0;
}

server.cc作为读端用于接收写端的输入并打印,具体内容如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include "commn.hpp"

int main()
{
    umask(0);
    // 创建管道文件
    int n = mkfifo(pipename.c_str(), mode);
    if (n < 0)
    {
        std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
        exit(1);
    }
    std::cout << "create fifo file success" << std::endl;

    // 以读方式打开管道文件
    int rfd = open(pipename.c_str(), O_RDONLY);
    if (rfd < 0)
    {
        std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
        exit(2);
    }

    // 进行通信
    while (true)
    {
        char buffer[NUM];

        ssize_t size = read(rfd, buffer, sizeof(buffer) - 1);
        buffer[size] = 0;
        if (size > 0)
        {
            std::cout << "client send me :" << buffer << std::endl;//输出接收的信息
        }
        else if (size == 0)
        {
            std::cout << "client quit, me too!" << std::endl;
            break;
        }
        else
        {
            std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;
            break;
        }
    }

    close(rfd);
    unlink(pipename.c_str()); // 删除文件
    return 0;
}

编译代码运行查看结果:

[Linux]进程间通信--管道,Linux,linux,服务器,网络,运维,c语言文章来源地址https://www.toymoban.com/news/detail-708894.html

到了这里,关于[Linux]进程间通信--管道的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux网络编程:socket、客户端服务器端使用socket通信(TCP)

    socket(套接字),用于网络中不同主机间进程的通信。 socket是一个伪文件,包含读缓冲区、写缓冲区。 socket必须成对出现。 socket可以建立主机进程间的通信,但需要协议(IPV4、IPV6等)、port端口、IP地址。          (1)创建流式socket套接字。                 a)此s

    2024年02月11日
    浏览(65)
  • 【Linux】进程通信之匿名管道通信

    我们往往需要多个进程协同,共同完成一些事情。 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源。 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。

    2024年04月14日
    浏览(46)
  • 【Linux】进程通信之管道通信详解

    🍎 作者: 阿润菜菜 📖 专栏: Linux系统编程 其实管道通信是Unix中最古老的进程间通信的形式了: 管道通信是一种进程间通信的方式,它可以让一个进程的输出作为另一个进程的输入,实现数据的传输、资源的共享、事件的通知和进程的控制。 管道通信分为两种类型:匿名

    2023年04月19日
    浏览(41)
  • Linux——进程间通信&&管道

    📘北尘_ :个人主页 🌎个人专栏 :《Linux操作系统》《经典算法试题 》《C++》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 数据传输:一个进程需要把他的数据传给另外一个进程。 资源共享:多个进程之间共享同样的资源。 通知事件:一个进程需要向另一个或一组

    2024年04月09日
    浏览(44)
  • Linux——进程间通信(管道)

    目录 进程通信的目的 管道 见见猪跑(举个例子) 文件描述符fd与管道的关系(深度理解管道) 什么是管道?  匿名管道 pipe函数概述 父子进程通信时与文件描述符的关系图(理解pipe函数的关键) pipe函数的使用  管道读写规则 管道的大小 自测  使用man 7 pipe查看 使用ulimit -a查看 管

    2024年02月03日
    浏览(88)
  • Linux——进程间通信、管道

    进程间的通信就是 在不同进程之间传播或交换信息。 举个例子: 古时,两军交战不斩来使; 因为两军互相是独立的,所以使节就是两军之间传话的进行传话的; 而在OS中,进程之间也是相互独立的,但某项工作并不是一个进程就可以完成,而是多个进程之间相互协助完成;

    2024年02月22日
    浏览(42)
  • Linux进程通信:无名管道

    (1)数据传输:进程间数据传输; (2)通知事件:一个进程向另一个或一组进程发送消息,通知某个事件的发生(如子进程终止时需通知父进程); (3)资源共享:多个进程共享资源,需要内核提供同步互斥机制; (4)进程控制:某进程需要控制另一个进程的执行(如

    2023年04月24日
    浏览(54)
  • [Linux]进程间通信--管道

    数据传输:一个进程需要将它的数据发送给另一个进程 。 资源共享:多个进程之间共享同样的资源。 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 进程控制:有些进程希望完全控制另一个进程的执

    2024年02月09日
    浏览(39)
  • linux——进程间通信——管道

     ✅1主页::我的代码爱吃辣 📃2知识讲解:Linux——进程间通信——管道通信 ☂️3开发环境:Centos7 💬4前言:进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。 目录 一.什么是进程间通信 二.进程间通信目的  三.进程间通信发展 四.什么是管道

    2024年02月08日
    浏览(42)
  • 【Linux】进程通信 — 管道

    从本章开始,我们开始学习进程通信相关的知识,本章将来详细探讨一下管道,学习匿名管道和命名管道的原理和代码实现等相关操作。目标已经确定,接下来就要搬好小板凳,准备开讲了…🙆🙆🙆🙆 在我们之前的学习中,我们知道进程是具独立性的。但是不要以为进程

    2024年02月16日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包