unix环境高级编程——进程间通信之管道

2023-10-29

本期主题:
unix环境高级编程——进程间通信之管道



1.什么是管道

1.定义:

当从一个进程连接数据流到另一个进程时,使用术语管道(pipe)。
管道是UNIX系统中IPC的最古老形式,所有UNIX都提供这种通信机制。对于大多数linux用户来说,管道在shell中其实非常熟悉,很多命令的连接就是通过管道来完成的。例如

$ cmd1 | cmd2

2.管道的局限性

  • 历史上,管道都是半双工的,数据只能在一个方向上流动,但是对于实际使用而言,进程的半双工管道意义作用其实并没有非常大;
  • fork之后的管道只能在父进程与子进程之间使用,通常的做法是,管道由一个进程创建,创建完之后调用fork,这样父进程与子进程就能使用管道通信了;

带着这两个问题思考,理解后面UNIX发展的FIFO和socket是怎么解决这两个问题的。

2.进程管道

这一章主要描述在单个进程中的管道。

1.popen和pclose

NAME
       popen, pclose - pipe stream to or from a process

SYNOPSIS
       #include <stdio.h>

       FILE *popen(const char *command, const char *type);

       int pclose(FILE *stream);

popen函数允许一个进程启动另外一个进程,并且可以通过管道读或者写这个新进程的数据,command参数就是要运行的程序,type参数就是r或者w,代表是要读还是写这个进程。
popen返回的是FILE文件流指针,可以用标准IO (fread、fwrite)等去操作。
看一个例子,使用popen从读取"uname -a"命令的所有信息。

#include <stdio.h>


int main(void)
{
    int char_bytes = 0;
    FILE *read_fp;
    char char_buf[1024] = { 0 };

    read_fp = popen("uname -a", "r");
    char_bytes = fread(char_buf, sizeof(char), 1024, read_fp);

    if (char_bytes > 0)
    {
        printf("pipe read is \n %s \n", char_buf);
    }
    pclose(read_fp);
}

jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ./a.out 
pipe read is 
Linux ubuntu 5.11.0-34-generic #36~20.04.1-Ubuntu SMP Fri Aug 27 08:06:32 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

2.使用popen的弊端

从上面的例子可以看出,使用popen有一个弊端:

每次的popen调用,既需要启动 使用popen调用的程序,还需要再启动一个 shell(例如上面这个例子里的"uname -a"),这样导致了popen将启动两个进程,从系统资源角度来看,popen的调用成本高。

因此引出了 pipe函数,pipe函数不需要启动shell.

3.pipe函数

pipe函数原型:

NAME
       pipe — create an interprocess channel

SYNOPSIS
       #include <unistd.h>

       int pipe(int fildes[2]);

输入参数是两个文件描述符,即在这两个文件描述符间建立管道。

注意:这个接口只能从fd[1]写,然后从fd[0]读,不能反过来,反过来可能会发生未定义的错误。

Data can be written to the file descriptor fildes[1] and read from the file descriptor fildes[0].  A read on the file descriptor fildes[0] shall access data written to the  file
       descriptor fildes[1] on a first-in-first-out basis. It is unspecified whether fildes[0] is also open for writing and whether fildes[1] is also open for reading.

看下面这个例子,如果从fd[1]写,从fd[0]读,会有问题

int main(void)
{
    int fd[2];
    int data_bytes = 0;

    if (pipe(fd) == 0)
    {
        printf("pipe sucessful. \n");
        char data[] = "hello world";
        char buff[1024];

        memset(buff, '\0', sizeof(buff));
        data_bytes = write(fd[0], data, strlen(data));
        printf("write data bytes %d\n", data_bytes);

        data_bytes = read(fd[1], buff, 1024);
        printf("read data bytes %d\n", data_bytes);
        printf("read is %s\n", buff);
    }
    else
    {
        printf("pipe failed. \n");
    }
}

jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ./a.out 
pipe sucessful. 
write data bytes -1
read data bytes -1
read is 

如果调换一下写和读,则输出正确结果
jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ./a.out 
pipe sucessful. 
write data bytes 11
read data bytes 11
read is hello world

1.单进程管道通信

上面这个例子是在单个进程中进行管道通信,用下图可以表示
在这里插入图片描述

2.跨越fork调用的管道

前面我们所讲的还是在单个进程之间的管道,管道的真正优势体现在,当你想在两个进程之间传递数据的时候。
当程序用fork调用产生新进程时,原来的文件描述符仍然是保持着打开状态,因此如果在原来的进程中先建立好管道,再使用fork,这样就能保证新进程中也有管道,即可通过管道在两个进程中传递数据。

看一个实际的例子,先建立管道,然后父进程中写,子进程中读:

int main(void)
{
    int fd[2];
    int data_bytes = 0;
    char data[] = "hello world";
    char buff[1024];

    memset(buff, '\0', sizeof(buff));
    pid_t fork_result;

    if (pipe(fd) == 0)
    {
        printf("pipe sucessful. \n");
        fork_result = fork();

        if (fork_result < 0)
        {
            printf("fork failed!\n");
        }
        else if (fork_result == 0)
        {
            pid_t child_pid;
            child_pid = getpid();
            data_bytes = read(fd[0], buff, 1024);
            printf("pid is %d, child process,read is %s\n", child_pid,buff);
        }
        else
        {
            int status;
            pid_t parent_pid;
            pid_t wpid;
            parent_pid = getpid();
            data_bytes = write(fd[1], data, strlen(data));
            wpid = wait(&status); //等待子进程结束
            printf("pid is %d, parent process, wpid is %d\n", parent_pid, wpid);
        }
    }
}

jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ./a.out 
pipe sucessful. 
pid is 20888, child process,read is hello world
pid is 20887, parent process, wpid is 20888

在这里插入图片描述

4.FIFO管道

FIFO也被称为命名管道,前面我们所提到的管道是无名管道,这两种管道之间的主要差别在于:
无名管道只能在相关的进程之间通信,即这两个进程需要有共同的祖先,而FIFO管道可以在不相关的进程之间也交换数据。

1.创建FIFO文件

shell中有命令能够创建FIFO,mkfifo命令

名称
       mkfifo - 创建 FIFO(命名管道)

概述
       mkfifo [选项]... 名称...

描述
       使用给定的名称创建命名管道(FIFO)。
//看个具体的例子
jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ mkfifo test_fifo
jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ls -lF test_fifo 
prw-rw-r-- 1 jason jason 0 Nov  6 09:47 test_fifo|
//文件类型p和最后的|符号,代表这是一个管道

也可使用API来创建FIFO

mkfifo, mkfifoat - make a FIFO special file (a named pipe)

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>

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

2.访问FIFO文件

FIFO在linux系统中其实也是一种文件,不过是管道类型的特殊文件,这个与前面讲的pipe不同,pipe函数的参数是文件描述符,而这里的FIFO是一个文件,需要用过文件IO的接口open来打开,从而获取到文件描述符。

1.看一个简单例子,直接访问FIFO文件
通过cat直接访问FIFO文件

jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ cat test_fifo&
[1] 21881
jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ echo "helloworld" > test_fifo 
jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ helloworld

[1]+  已完成               cat test_fifo

可以看出这是一个阻塞式的行为,需要把cat test_fifo命令放在后台运行,直到有写这个FIFO的,才能读取出来。

2.使用open方式打开管道
使用open方式打开FIFO管道有两个特点
1.程序不能以O_RDWR模式打开FIFO文件进行读写操作,因为FIFO通常是用来传递单向数据;
2.open_flag加上O_NONBLOCK标志位与否,对应的现象不一样,具体看下面两个例子:

open的使用有两种标志位,一种是非阻塞标志位,另外一种是阻塞标志位,默认就是阻塞状态。
(1)使用默认模式(阻塞模式)的demo:
结论:使用该模式时,open会阻塞在打开FIFO,需要有一个进程读,另一个进程写,先打开的会阻塞住;
下面的例子,读的进程后台运行,阻塞住了,直到写的进程开始运行;

int main(int argc, char *argv[])
{
    int i = 0;
    int open_mode = 0;
    int fd = 0;
    int ret = 0;

    if (argc < 2)
    {
        printf("Wrong, too less argv!\n");
        return -1;
    }

    for (i = 1; i < argc; i++)
    {
        if (strcmp(argv[i], "O_RDONLY") == 0)
        {
            open_mode |= O_RDONLY;
        }
        if (strcmp(argv[i], "O_WRONLY") == 0)
        {
            open_mode |= O_WRONLY;
        }
        if (strcmp(argv[i], "O_NONBLOCK") == 0)
        {
            open_mode |= O_NONBLOCK;
        }
    }

    printf("process %d opening fifo\n", getpid());
    ret = open("test_fifo", open_mode);
    if (ret < 0)
    {
        printf("wrong fd!\n");
        return -1;
    }

    printf("process %d exit, ret is %d\n", getpid(), ret);
}

jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ./a.out O_RDONLY&
[1] 25167
jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ process 25167 opening fifo

jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ./a.out O_WRONLY
process 25168 opening fifo
process 25167 exit, ret is 3
process 25168 exit, ret is 3
[1]+  已完成               ./a.out O_RDONLY

(2)使用非阻塞模式:
结论:使用非阻塞模式,open不会等待,而会立即返回;

jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ ./a.out O_RDONLY O_NONBLOCK &
[1] 25314
jason@ubuntu:~/WorkSpace/0.Unix_AP/15.pipe$ process 25314 opening fifo
process 25314 exit, ret is 3

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

unix环境高级编程——进程间通信之管道 的相关文章

  • 如何根据 HTTP 请求使用 Python 和 Flask 执行 shell 命令并流输出?

    下列的这个帖子 https stackoverflow com questions 15092961 how to continuously display python output in a webpage 我能够tail f网页的日志
  • 如何在 shell 脚本中并行运行多个实例以提高时间效率[重复]

    这个问题在这里已经有答案了 我正在使用 shell 脚本 它读取 16000 行的输入文件 运行该脚本需要8个多小时 我需要减少它 所以我将其划分为 8 个实例并读取数据 其中我使用 for 循环迭代 8 个文件 并在其中使用 while
  • Linux中的CONFIG_OF是什么?

    我看到它在很多地方被广泛使用 但不明白在什么场景下我需要使用它 What is 配置 OF OF 的全名是什么 打开固件 这是很久以前发明的 当时苹果公司正在生产基于 PowerPC CPU 的笔记本电脑 而 Sun Microsystem
  • Linux中的定时器类

    我需要一个计时器来以相对较低的分辨率执行回调 在 Linux 中实现此类 C 计时器类的最佳方法是什么 有我可以使用的库吗 如果您在框架 Glib Qt Wx 内编写 那么您已经拥有一个具有定时回调功能的事件循环 我认为情况并非如此 如果您
  • Linux 中的动态环境变量?

    Linux 中是否可以通过某种方式拥有动态环境变量 我有一个网络服务器 网站遵循以下布局 site qa production 我想要一个环境变量 例如 APPLICATION ENV 当我在 qa 目录中时设置为 qa 当我在生产目录中时
  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • Android 时钟滴答数 [赫兹]

    关于 proc pid stat 中应用程序的总 CPU 使用率 https stackoverflow com questions 16726779 total cpu usage of an application from proc
  • 如何在数组中存储包含双引号的命令参数?

    我有一个 Bash 脚本 它生成 存储和修改数组中的值 这些值稍后用作命令的参数 对于 MCVE 我想到了任意命令bash c echo 0 0 echo 1 1 这解释了我的问题 我将用两个参数调用我的命令 option1 without
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成
  • 添加要在给定命令中运行的 .env 变量

    我有一个 env 文件 其中包含如下变量 HELLO world SOMETHING nothing 前几天我发现了这个很棒的脚本 它将这些变量放入当前会话中 所以当我运行这样的东西时 cat env grep v xargs node t
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 如何在Linux内核源代码中打印IP地址或MAC地址

    我必须通过修改 Linux 内核源代码来稍微改变 TCP 拥塞控制算法 但为了检查结果是否正确 我需要记录 MAC 或 IP 地址信息 我使用 PRINTK 函数来打印内核消息 但我感觉很难打印出主机的MAC IP地址 printk pM
  • PHP 从命令行启动 gui 程序,但 apache 不启动

    首先 我阅读了有类似问题的人的一些帖子 但所有答案都没有超出导出 DISPLAY 0 0 和 xauth cookies 这是我的问题 提前感谢您的宝贵时间 我开发了一个小库 它使用 OpenGL 和 GLSL 渲染货架 过去几天我将它包装
  • NPTL 和 POSIX 线程有什么区别?

    NPTL 和 POSIX 线程之间的基本区别是什么 这两者是如何演变的 POSIX 线程 pthread 不是一个实现 它是几个函数的 API 规范 纸上的标准 英文 其名称以pthread 以及定义在
  • 如何在apache 2.4.6上安装apxs模块

    我刚刚用过apt get update我的 apache 已更新为2 4 6 我想安装 apxs 来编译模块 但收到此错误 The following packages have unmet dependencies apache2 pre
  • C语言中如何通过内存地址映射函数名和行号?

    如何用 GCC 中的内存地址映射回函数名称和行号 即假设一个 C 语言原型 void func Get the address of caller maybe this could be avoided MemoryAddress get
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • [APUE]fork后父进程和子进程是否共享相同的文件偏移量?

    在 APUE 第 8 3 节中fork function 关于父子进程之间的文件共享 它说 It is important that the parent and the child share the same file offset 在
  • 将 jar 作为 Linux 服务运行 - init.d 脚本在启动应用程序时卡住

    我目前正在致力于在 Linux VM 上实现一个可运行的 jar 作为后台服务 我已经使用了找到的例子here https gist github com shirish4you 5089019作为工作的基础 并将 start 方法修改为
  • 如何通过ssh检查ubuntu服务器上是否存在php和apache

    如何通过ssh检查Ubuntu服务器上apache是 否安装了php和mysql 另外如果安装的话在哪个目录 如果安装了其他软件包 例如 lighttpd 那么它在哪里 确定程序是否已安装的另一种方法是使用which命令 它将显示您正在搜索

随机推荐

  • python 读取pkl文件

    python 读取imdb full pkl文件 import cPickle as pickle f open D keras imdb full pkl info pickle load f print info show file
  • vue+electron 跨平台桌面应用开发实战教程

    一 创建项目 1 1 安装vue cli 先查看是否已经安装了vue cli vue cli的版本是什么 查看版本命令 vue version 如果版本叫老 可以直接卸载 再安装最新版本 卸载命令 npm uninstall vue cli
  • 最深情的告白——郁金香(Python实现)

    目录 1 最深情的告白 2 即兴赞之 2 1 李小白言郁金香 2 2 郁金香般的姑娘 2 3 荷兰的郁金香 3 Python代码实现 3 1 郁金香的芬芳 3 2 我俩绚丽多姿的风景 1 最深情的告白 曾经以为 她爱玫瑰 然后我画了好几种
  • Python简介、历史及优缺点

    Python是一种高级 解释型 面向对象的编程语言 它由Guido van Rossum于1989年在荷兰创造 并于1991年发布 Python的设计哲学强调代码的可读性和简洁性 它具有清晰简洁的语法 使得它易于学习和使用 Python的历
  • linux新建用户代码,Linux_用dsadd添加用户的代码,描述: 此工具命令将一些具体 - phpStudy...

    用dsadd添加用户的代码 描述 此工具命令将一些具体的对象类型添加到目录 dsadd 命令 dsadd computer 将计算机添加到目录 dsadd contact 将联系人添加到目录 dsadd group 将组添加到目录 dsad
  • DB2 import和load

    Import和Load 都可以将数据导入到DB2服务器中 但是2者是有很大区别的 Import 其实执行了SQL 的INSERT 操作 和INSERT 操作一样 Import 执行的时候会激活触发器 所有的约束会强制实现 而且会使用数据库的
  • Linux系统如何正确的关机、重启

    1 在使用Linux系统时 需要注意 linux系统可同时登陆多个账户使用 所以在关机和重启时都需要对每个登陆的账户进行数据保存 楼主在使用保存命令时为了防止出现保存失误 一般使用三个sync super localhost sync sy
  • Matlab求范数

    对 p 2 这称为弗罗贝尼乌斯范数 Frobenius norm 或希尔伯特 施密特范数 Hilbert Schmidt norm 不过后面这个术语通常只用于希尔伯特空间 这个范数可用不同的方式定义 这里 A 表示 A 的共轭转置 i 是
  • leetcode704. 二分查找

    给定一个 n 个元素有序的 升序 整型数组 nums 和一个目标值 target 写一个函数搜索 nums 中的 target 如果目标值存在返回下标 否则返回 1 示例 1 输入 nums 1 0 3 5 9 12 target 9输出
  • 计算机缺失d3dcompiler43.dll,电脑d3dcompiler43.dll文件丢失怎么解决?

    最近有很多小伙伴在使用win7系统的时候 遇到了系统提示d3dcompiler43 dll文件丢失的问题 那么 这个问题该怎么解决呢 这种文件的丢失一般影响到玩游戏 因为文件没有及时的更新 其实只要我们重新网上下载同一个该文件就可以了 下面
  • 编程的:23种设计模式

    color red 1 工厂模式 客户类和工厂类分开 消费者任何时候需要某种产品 只需向工厂请求即可 消费者无须修改就可以接纳新产品 缺点是当产品修改时 工厂类也要做相应的修改 如 如何创建及如何向客户端提供 color color gre
  • Ubuntu18.04配置darknet环境实现YOLOv4目标检测(五)——darknet YOLOv4和YOLOv4-tiny模型转ONNX转TensorRT部署

    文章目录 1 github开源代码 2 darknet模型转ONNX模型 3 ONNX模型转TensorRT模型 3 1 概述 3 2 编译 3 3 运行 4 推理结果 1 github开源代码 开源代码位置在这里 darknet转ONNX
  • 基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(一)简介

    笔者写的这个教程集 是一项带引号的 翻译 类教程 Joey De Vries 的现代OpenGL教程是我看过最好的图形学教程 没有之一 这是教程地址https learnopengl cn github io 01 20Getting 20
  • Polyman 初使用-01

    软件安装很easy 不赘述 启动软件 打开 edf文件 左侧是不同的通道名称 每页是一个epoch 也就是 30s 左下角的三角可以翻页 速度有点慢 我们可以拖动右上方方框里的白色竖条进行时间拉伸 红色填充是每个阶段的标签 这个是额外引入的
  • 由于您访问的url有可能对网站造成安全威胁_Web常见安全漏洞-XSS攻击

    XSS攻击 跨站脚本攻击 Cross Site Scripting 为了不和层叠样式表 Cascading Style Sheets CSS 的缩写混淆 故将跨站脚本攻击缩写为XSS XSS是一种常见的web安全漏洞 它允许攻击者将恶意代码
  • 区块链风暴来袭,这五大行业将受到强烈冲击

    自从第一枚比特币诞生以来 加密货币就代表了经济实现重大飞跃的一次机会 尽管比特币出现了 没有中心化的机构 并且成为了传统货币的数字化选择 驱动加密货币运作的底层计算机协议则是从之后才被认为具备大量其他的应用场景 从分布式账本的不可更改性到加
  • salt-key收集的主机名与实际主机名不一致

    salt key收集的主机名与实际主机名不一致 一 问题背景 root host 39 108 217 12 minions salt key L iZbp150ikdomqe3b32qaubZ izwz9f8xrvty50quc2gq50
  • Redhat6.5安装vnc服务远程桌面

    环境 操作系统 Redhat6 5 已安装桌面 远程工具 Xshell6 VNC服务 可以远程将桌面输出 tigervnc tigervnc server ssh远程到服务器之后 yum安装tigervnc tigervnc server
  • CKA认证题型解析

    文章目录 0 前言 1 RBAC 授权 2 节点设置不可用 3 升级 K8s 版本 注意 4 etcd 备份与恢复 5 网络策略 查看官网 NetworkPolicy 6 SVC 暴露应用 7 Ingress 查看官网 ingress 8
  • unix环境高级编程——进程间通信之管道

    本期主题 unix环境高级编程 进程间通信之管道 管道 1 什么是管道 1 定义 2 管道的局限性 2 进程管道 1 popen和pclose 2 使用popen的弊端 3 pipe函数 1 单进程管道通信 2 跨越fork调用的管道 4