【linux进程间通信(一)】匿名管道和命名管道

简介: 【linux进程间通信(一)】匿名管道和命名管道

1. 前言

众所周知,进程运行是具有独立性的,

想要进程间进行通信就要打破这种

独立性,而进程间通信的本质其实是

让不同的进程看见同一份资源!

本章重点:

本篇文章会介绍进程间通信中常见
的几种方式,并且着重讲解匿名管道
和命名管道的这两种通信手段的原理
和代码的实现.


2. 进程间通信的方法

首先通信种类分为三大类:

  • 管道
  • system V
  • POSIX

当然网络通信的本质其实也是进程

间的通信,但是本篇文章的重点是

利用管道通信!!!


3. 管道的简单介绍

首先要回答什么是管道,在学习

Linux指令时我们使用过竖划线 |

也就是管道,把从一个进程连接

到另一个进程的数据流称为“管道”

在管道通信中,管道的本质其实就是
一个被系统打开的文件,然而用管道
进行通信的本质就是让不同的进程
看见相同的资源(文件)

并且管道是单向通信的,即一个进程

不能同时从一个管道中读取和写入,

读取和写入要对应两个不同的管道!


4. 匿名管道

对于匿名管道来说,通常用于父子
进程之间的通信,在父进程使用
pipe创建好管道后,再使用fork创建
子进程,此时子进程会将父进程的
文件描述符表给拷贝过来,也就天然
的拥有这两个管道文件了!

并且此时我们会面临两个问题:

  1. 读取方将文件关闭了会发送什么?
  2. 读取方在文件中没有数据时会干什么?
  • 读端关闭后,写端进程会终止
  • 当管道无数据时读端会阻塞

有了上面的经验后,现在可以编码验证一下:

int main()
{
    //创建管道
    int pipefd[2]={0}; //0下标表示读取端,1下标表示写入端
    int n = pipe(pipefd);
    assert(n!=-1);
    (void)n;
#ifdef DEBUG//条件编译
    cout<<"[0]: "<<pipefd[0]<<" "<<"[1]: "<<pipefd[1]<<endl;
#endif
    //创建子进程
    pid_t id = fork();
    assert(id!=-1);
    if(id==0)//子进程, 构建单向通信
    {
        close(pipefd[1]);
        char buffer[1024];
        while(1)
        {
            ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
            if(s>0)
            {
                buffer[s]=0;
                cout<<"father# "<<buffer<<endl;
            }
            else//read的返回值等于0代表父进程的管道文件已经close了
            {
                cout<<"写入结束,子进程退出";
                break;
            }
        }
        exit(0);
    }
    //父进程写入,子进程读取
    close(pipefd[0]);
    string str = "我在给子进程发信息";
    int count=0;
    char send_buffer[1024];
    while(count<=5)
    {
        //构建一个变化的字符串
        snprintf(send_buffer, sizeof(send_buffer),"%s[%d]: %d",str.c_str(),getpid(),count++);//往缓冲区里写入数据
        //写入到管道中
        write(pipefd[1],send_buffer,sizeof(send_buffer));
        sleep(1);
    }
    close(pipefd[1]);
    pid_t ret = waitpid(id,NULL,0);
    assert(ret > 0);
    return 0;
}

总结管道的特点:

  • 管道常用于父子进程间的通信
  • 管道是面向字节流的服务
  • 管道是基于文件的,管道的生命周期随进程
  • 管道是单向通信的
  • 写快读慢,写满管道后不能再写了
  • 写慢读快,管道没有数据时,读端要等待
  • 写端关闭,读端会读到0,标识结束
  • 读端关闭,写端继续写会终止进程

5. 命名管道

匿名管道的一个限制就是必须是在

有血缘关系的进程间才能通信,使用

命名管道可以解决这个问题

匿名管道和命名管道的区别:

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

命名管道的打开规则:

  • 如果当前打开操作是为读而打开FIFO时
  1. O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  2. O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时
  1. O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  2. O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

命名管道简单通信:

读端代码:

#define SIZE 1024
#define MODE 0666
string ipcpath = "./fifo.ipc";
static void getmessage(int fd)
{
    //编写通信代码
    char buffer[SIZE];
    while(1)
    {
        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)//写端关闭,并且读到end
        {
            cerr<<"["<<getpid()<<"]"<<"read end"<<endl;
            break;
        }
        else//读取失败
        {
            perror("read");
            break;
        }
    }
}
int main()
{
    //创建管道文件
    int n = mkfifo(ipcpath.c_str(),MODE);
    if(n<0)
    {
        perror("mkfifo");
        exit(1);
    }
    //文件操作
    int fd = open(ipcpath.c_str(),O_RDONLY);//这里必须等待client进程将此管道文件打开后才能执行下面的代码
    if(fd<0)
    {
        perror("open");
        exit(2);
    }
    for(int i=0;i<4;i++)
    {
        int id = fork();
        if(id==0)
        {
            getmessage(fd);
            exit(0);
        }
    }
    
    close(fd);
    return 0;
}

写端代码:

#define SIZE 1024
#define MODE 0666
string ipcpath = "./fifo.ipc";
int main()
{
    //client不用自己创建管道文件,只需获取文件即可
    int fd = open(ipcpath.c_str(),O_WRONLY);
    if(fd<0)
    {
        perror("open");
        exit(1);
    }
    //通信过程
    string buffer;
    while(1)
    {
        cout<<"please Enter message: ";
        getline(cin,buffer);
        write(fd,buffer.c_str(),buffer.size());
    }
    close(fd);
    return 0;
}

6. 总结以及拓展

虽然匿名管道和命名管道已经包含了

对于有血缘关系和无血缘关系的进程

的通信,但是毕竟调用read和write这些

系统调用接口会不断的在用户态和内核

态进行切换,比较消耗资源,所以管道不是

最好的!

拓展: 为什么读端关闭后,写端进程会终止?

这是因为当读端关闭后,写端再使用
write操作会产生SIGPIPE信号,此进
程收到此信号进而会终止进程,可以
使用捕捉信号的方式验证这一结论


🔎 下期预告:共享内存通信方式 🔍


相关文章
|
1月前
|
消息中间件 存储 Linux
|
2月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
41 0
Linux c/c++之IPC进程间通信
|
2月前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
38 0
Linux c/c++进程间通信(1)
|
4月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
79 0
|
6月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
137 13
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
5月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
192 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
4月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
5月前
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
176 1
|
5月前
|
小程序 Linux
【编程小实验】利用Linux fork()与文件I/O:父进程与子进程协同实现高效cp命令(前半文件与后半文件并行复制)
这个小程序是在文件IO的基础上去结合父子进程的一个使用,利用父子进程相互独立的特点实现对数据不同的操作
128 2