热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

linux系统编程:使用管道实现进程间的通信(附C++实现代码)

文章目录什么是管道1.单工管道相关函数读数据写数据2.半双工管道相关函数文件描述符半双工双向通信代码实现思路3.FIFO半双工相关函数代码生成管道读出数据写入数据什么是管道管道&


文章目录

  • 什么是管道
  • 1. 单工管道
    • 相关函数
    • 读数据
    • 写数据
  • 2. 半双工管道
    • 相关函数
    • 文件描述符
    • 半双工双向通信代码实现思路
  • 3. FIFO半双工
    • 相关函数
    • 代码
      • 生成管道
      • 读出数据
      • 写入数据




什么是管道

管道,从名字上理解就知道它和数据传输有关。它是最基本的进程间通信机制,依据pipe系统函数来创建,从而完成数据传输。从实现原理上来说,管道是内核使用环形队列机制借助内核缓冲区实现的,它也可以认为是一个伪文件,它由两个文件描述符引用,一个为读端用于读数据,一个为写端用于写数据。

我们根据数据流向将管道分为三类,一为单工管道,数据流向是单向的,只能由某一个人接收信息,另一个人发送信息。二为半双工管道,双方都可以进行接收和发送数据,但是不能同时进行。三为全双工管道,这种通信方式是双方可以同时发送和接收信息,在本文中不涉及这种通信。


1. 单工管道


相关函数

下面我们列出关于管道的几个基本的函数。


功能函数格式参数含义返回值
打开管道FILE* popen (const char *command, const char *open_mode)1.command:打开的文件名 2. open_mode:访问该文件的模式(只读/只写)NULL->打开失败;非NULL->文件描述符
读取数据size_t fread ( void *buffer, size_t size, size_t count, FILE *stream)buffer:用于接收数据的内存地址 size:读取每个数据项的字节数 count : 数据项个数 stream:输入流>count->出错; 正数->真实读取的数据项个数
写入数据size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)buffer:写入数据的内存地址 size:读取每个数据项的字节数 count : 数据项个数 stream:目标文件指针>count->出错; 正数->真实读取的数据项个数
关闭管道int pclose(FILE *stream);stream: 文件描述符-1-> 成功; 0->失败

单工指的就是单向的通信。

  • popen会启动两个进程,首先会启动了一个shell命令,然后会开启我们传给popen函数的命令进程。
  • popen("./output","r"):以读的方式打开可执行文件./a.out
  • popen("./output","w"):以写的方式打开可执行文件./a.out
  • 相比较execsystem函数,popen可以进行进程间的通信,可以传输数据。
  • 数据不可以在管道中反复读取


读数据


此时相当于从终端读取数据


相关代码如下:

#include
#include using namespace std;int main(){FILE* pf = popen("./output","r"); // 打开某个管道if(pf != NULL){char buff[30] = {&#39;\0&#39;};fread(buff, 1, sizeof(buff), pf); // 读出数据cout << "read:" << buff <<endl;pclose(pf); // 关闭管道pf = NULL;}
}

写数据


此时就相当于写数据写到终端


#include
#include using namespace std;int main(){FILE* pf = popen("./input","w");if(pf != NULL){char buff[] = "abcdef1234";fwrite(buff, 1, sizeof(buff), pf);cout << "read:" << buff <<endl;pclose(pf);pf = NULL;}
}

2. 半双工管道

半双工管道意思就是指,数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。


相关函数


函数格式相关参数意义该函数功能返回值意义
int pipe(int filedes[2])filedes[0]->读 ; filedes[1]->写创建管道,获取文件操作符-1->失败; 0->成功
size_t write(int fd, const void *buf, size_t nbyte)fd ->文件描述符;buf ->写入数据的内存单元;nbyte->写入文件指定的字节数读取数据-1->失败;正数->写入的字节数
size_t read(int fd, void *buf, size_t count)fd ->文件描述符;buf ->读取数据的内存单元;写入数据-1->失败;0-> 无数据;正数->读取的字节数
int fcntl(int fd, int cmd, long arg)fd ->文件描述符;cmd->控制管道命令;arg -> 描述符状态控制设置是否进行阻塞
close(filedes)filedes->文件操作符关闭管道

注意:

  • cmd命令的种类有:F_GETFL:获取文件描述符状态;F_SETFL:设置文件描述符状态;
  • 描述符的状态有两种,O_NONBLOCK:非阻塞;O_BLOCK:阻塞
  • 不需要启动额外的shell进程
  • 可以理解为一次性启动两个管道,一个管道用于读,一个管道用于写

我们接下来的代码并不是直接给出实现半双工双向通信的代码,而是单向通信的功能开始,进行改进实现这种半双工双向的通信。


文件描述符

文件描述附用于读写数据,是系统用于提供操作文件的ID,一个文件文件描述符表示对一个文件的操作。
在这里插入图片描述
linux内核中使用三个关联的数据结构,从而打开文件描述符对应的文件,其中的文件表中存放的是文件的相关信息,V-节点表里面存放的是文件中真实的数据,具体如下:

在这里插入图片描述比较特别的是,父子进程前打开的文件,对于父子进程而言关系如下:

在这里插入图片描述


半双工双向通信代码实现思路

首先我们实现一个简单功能:让父进程利用管道,读到了我们从终端键入的数据abcde,并将其放入了字符串数组buff中。

此时的管道通信图如下:

在这里插入图片描述为了防止误用,我们常常会在父子进程中分别关闭不用的读写功能,管道示意图如下:

在这里插入图片描述

测试代码如下:

#include
#include
#include
#include using namespace std;int main(){int fd[2]; pipe(fd); // 获得文件描述符,文件描述符用于操作通道cout << getpid() << endl;if(0 == fork()){close(fd[0]);cout << getpid() << ":";string s;cin >> s; // 阻塞,等待终端输入数据write(fd[1], s.c_str(),s.size()+1);close(fd[1]);}else{close(fd[1]);char buff[30] = {&#39;\0&#39;};read(fd[0],buff,sizeof(buff)); // 阻塞,等待管道写入数据cout << getpid() << ":" << buff << endl;close(fd[0]);}
}

运行结果如下:
在这里插入图片描述


注意:这部分要注意阻塞的出现,当管道中没有数据的时候,read函数会发生阻塞,等待管道读入数据。


但是read这里系统增添的阻塞使得进程在阻塞的过程中无法执行任何任务,为了提高效率,我们往往会取消这里的阻塞,让进程在等待的时间内处理其他的任务,使用一个while循环来进行轮询,代码如下:

#include
#include
#include
#include using namespace std;int main(){int fd[2]; pipe(fd); // 获得文件描述符,文件描述符用于操作通道cout << getpid() << endl;if(0 == fork()){cout << getpid() << ":";string s;cin >> s; // 阻塞,等待终端输入数据write(fd[1], s.c_str(),s.size()+1);}else{fcntl(fd[0],F_SETFL, O_NONBLOCK); // 取消阻塞char buff[30] = {&#39;\0&#39;};while(-1 == read(fd[0],buff,sizeof(buff))){sleep(1);cout << "wait..." << endl;}cout << getpid() << ":" << buff << endl;}close(fd[0]);close(fd[1]);
}

最终我们实现:让父进程读完数据之后写数据,然后让子进程读出该数据,子进程读出数据之后写数据让父进程进行读取。为了实现双方的相互通信,我们需要开启两套管道,管道通信示意如下:
在这里插入图片描述

实现代码如下:

#include
#include
#include
#include using namespace std;int main(){int fd1[2]; // 无法使用一个管道实现多次半双工通信,// 半双工:不是实事双向通信,在一方发送消息的时候另一方>再等待int fd2[2];pipe(fd1);pipe(fd2);cout << getpid() << endl;if(0 == fork()){for(;;){cout << getpid() << ":";string s;cin >> s; // 阻塞,等待终端输入数据write(fd1[1],s.c_str(),s.size()+1);char buff[30] = {&#39;\0&#39;};read(fd2[0],buff,sizeof(buff)); // 阻塞,等待管道写入数据cout << getpid() << ":" << buff << endl;}}else{for(;;){char buff[30] = {&#39;\0&#39;};while(-1 == read(fd1[0],buff,sizeof(buff))){ // 阻塞,等待>管道写入数据sleep(1);cout << "\r" << "wait..." << endl;} cout << getpid() << ":" << buff << endl;cout << getpid() << ":";string s;cin >> s;write(fd2[1], s.c_str(), s.size()+1);}}close(fd1[0]);close(fd1[1]);close(fd2[0]);close(fd2[1]);
}

运行结果如下:
在这里插入图片描述


3. FIFO半双工

在这部分,我们考虑到非亲缘进程的通信,为了能让两个没有亲缘关系的进程可以进行通信,我们首先要对管道起一个名字,从而使得这两个进程可以在同一个管道中进行读取数据,从而实现通信。


相关函数


函数功能函数格式参数意义
创建命名管道int mkfifo(pathname,mode)pathname->文件路径(该文件必须不存在);mode->该管道的访问权限
打开FIFO文件int open(const char *path, int mode)pathname->文件路径;mode->访问该管道的模式

注意:

  • 访问权限:一般为0666,指的是对拥有者、拥有组和其他人都可以进行读写的权限,具体权限的内容可以看关于linux文件的权限表示的内容。
  • FIFO文件:具有先进先出的性质。
  • 管道文件生成的时候要求输入路径名的格式是文件路径。


代码

我们需要创建三个文件,一个用于创建命名管道,另外两个分别模拟两个没有亲缘进程在该管道中进行数据的读取操作,具体代码如下:


生成管道

#include
#include
using namespace std;int main(){string name;cin >> name;mkfifo(name.c_str(),0666);
}

运行结果如下:
在这里插入图片描述


读出数据

#include
#include
#include
#include
#include using namespace std;int main(){string file;cin >> file;cout << "before open" << endl;int fd = open(file.c_str(),O_RDONLY);if(-1 == fd){perror("open pipe error");return 1;}cout << "after open" << endl;char buff[30] = {&#39;\0&#39;};read(fd, buff, sizeof(buff));cout << buff << endl;close(fd);
}

写入数据

#include
#include
#include
#include
#include using namespace std;int main(){string file;cin >> file;cout << "before open" << endl;int fd = open(file.c_str(),O_WRONLY);if(-1 == fd){perror("open pipe error");return 1;}cout << "after open" << endl;string s;cin >> s;write(fd,s.c_str(),s.size()+1);close(fd);
}

我们需要在两个shell里来测试该代码,运行两个读写文件生成可执行文件,在命令行g++ write.cpp -o write以及g++ read.cpp -o read生成可执行文件,最终运行结果如下:

我们需要先运行l两个可执行文件:
在这里插入图片描述./write的shell内输入想要传输的数据:

在这里插入图片描述
回车后我们就可以在./read的shell中看到我们刚才输入的数据了,如下:

在这里插入图片描述


推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文比较了eBPF和WebAssembly作为云原生VM的特点和应用领域。eBPF作为运行在Linux内核中的轻量级代码执行沙箱,适用于网络或安全相关的任务;而WebAssembly作为图灵完备的语言,在商业应用中具有优势。同时,介绍了WebAssembly在Linux内核中运行的尝试以及基于LLVM的云原生WebAssembly编译器WasmEdge Runtime的案例,展示了WebAssembly作为原生应用程序的潜力。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • Linuxchmod目录权限命令图文详解在Linux文件系统模型中,每个文件都有一组9个权限位用来控制谁能够读写和执行该文件的内容。对于目录来说,执行位的作用是控制能否进入或者通过 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了使用CentOS7.0 U盘刻录工具进行安装的详细步骤,包括使用USBWriter工具刻录ISO文件到USB驱动器、格式化USB磁盘、设置启动顺序等。通过本文的指导,用户可以轻松地使用U盘安装CentOS7.0操作系统。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
author-avatar
驰诗勋
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有