【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信号,此进
程收到此信号进而会终止进程,可以
使用捕捉信号的方式验证这一结论


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


相关文章
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
479 16
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
363 0
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
405 0
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
287 0
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
371 0
|
11月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
1068 1
二、Linux文本处理与文件操作核心命令
|
11月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
747 137
|
11月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1683 58
|
10月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
1614 2
|
11月前
|
Unix Linux 程序员
Linux文本搜索工具grep命令使用指南
以上就是对Linux环境下强大工具 `grep` 的基础到进阶功能介绍。它不仅能够执行简单文字查询任务还能够处理复杂文字处理任务,并且支持强大而灵活地正则表达规范来增加查询精度与效率。无论您是程序员、数据分析师还是系统管理员,在日常工作中熟练运用该命令都将极大提升您处理和分析数据效率。
880 16