Linux 练习六 (IPC 管道)

news2025/7/21 19:26:12

文章目录

  • 1 标准管道流
  • 2 无名管道(PIPE)
  • 3 命名管道(FIFO)
    • 3.1 创建删除管道文件
    • 3.2 打开和关闭FIFO文件
    • 3.3 管道案例:基于管道的客服端服务器程序

使用环境:Ubuntu18.04
使用工具:VMWare workstations ,xshell

  作者在学习Linux的过程中对常用的命令进行记录,通过思维导图的方式梳理知识点,并且通过xshell连接vmware中ubuntu虚拟机进行操作,并将练习的截图注解,每句话对应相应的命令,读者可以无障碍跟练。第六次练习的重点在于Linux的管道,这次是进程间的管道通信不同于练习四中介绍的管道文件。
  

1 标准管道流

  • 和文件操作的io流一样,管道也支持文件流模式。通过打开和关闭管道流的函数是popen和pclose。
#include <stdio.h>
FILE* popen(const char* command, const char* open_ mode);
int pclose(FILE* fp);
  • 函数popen:允许一个程序将另一个程序作为新的进程启动,并可以传递数据给它或者通过它接收数据。command字符串就是要运行的程序名。open_mode必须是“r”或者“w”,如果是“r“被调用程序的输出就可以被调用程序使用,调用程序使用返回的FILE* 文件流指针,就可以通过调用stdio函数库中的fread来读取被调用程序的输出。如果是“w”,则可以调用fwrite向被调用程序发送数据,而被调用程序可以在自己的标准输入上读取这些数据。
  • 函数pclose:关闭相关联的文件流。
//读取当前目录下file的内容
#include<stdio.h>
int main()
{
	FILE* fp = open("./file","r");
	char buf[128] = {0};
	while(fgets(buf,sizeof(buf),fp)){
		puts(buf);
	}
	pclose(fp);
	return 0;
}
//写一串字符串到标准管道流,统计buf单词数量(被调用程序必须阻塞等待标准输入)
#include<stdio.h>
int main()
{
	char buf[128] = {"apple orign banana man fale"};
	FILE* fp = popen("wc -w","w");//wc -w功能是统计字符串中单词的个数
	fwrite(buf,sizeof(buf),1,fp);//向被调用的wc -w命令所启动的程序发送buf内容
	pclose(fp);
	return 0;
}

2 无名管道(PIPE)

管道通信是linux进程通信的一种方式,例如可以使用ps -elf|grep ntp查询和ntp相关的管道
无名管道的特点:

  1. 只能在亲缘关系进程间通信(父子进程或者兄弟进程)
  2. 半双工通信
  3. 管道是特殊文件可以使用read、write,只能存在内存中
#include<unistd.h>
int pipe(int fds[2]);
  • 管道在程序中使用一对文件描述符表示,其中一个文件描述符有可读属性,一个有可写属性。fds[0]是可读,fds[1]是可写。函数pipe用于创建一个无名管道,如果成功,fds[0]中存放文件描述符,fds[1]存放可写文件描述符,并且函数返回0,否则返回-1。
  • 通过调用pipe获取这对打开的文件描述符后,一个进程就可以从fds[0]中读数据,而另一个进程就可以向fds[1]中写数据。两进程必须有几成关系,才能继承这对打开的文件描述符。
  • 管道文件不是真正的物理文件,存活在内存中不持久。当两进程都终止后,管道就自动消失。

在这里插入图片描述

//创建父子进程,创建无名管道,父进程写数据,子进程读数据
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h> 
#include <sys/types.h>
#include <sys/wait.h>

int main()
{	
	int fds[2];		//设置读和写两个文件描述符
	pipe(fds);		//使用pipe函数创建进程,并且将两个文件描述符传入参数
	printf("fds[0] = %d,fds[1] = %d\n",fds[0],fds[1]);
	char buf[32] = {'\0'};
	if(fork() == 0){ 		//表示子进程
		close(fds[1]);	//子进程关闭写操作
		sleep(2); 		//确保父进程有时间关闭读操作,并且向管道中写内容
		if(read(fds[0],buf,sizeof(buf))){	//将管道中的内容读到buf缓冲区中
			puts(buf);
			close(fds[0]);	//关闭子进程的读端
			exit(0);  		//结束子进程
		}
	}
	else{		//表示父进程
		close(fds[0]);				//父进程关闭读
		write(fds[1],"hello",6);	//从fds[1]向管道中写入hello
		waitpid(-1,NULL,0);		//等待子进程关闭
		//wait(NULL);			//和waitpid同等效果
		//write(fds[1],"world",6);	//此时会出现断开的管道因为子进程的读已经关闭了
		close(fds[1]);				//父进程关闭写
		exit(0);
	}
	
	return 0;
}
  • 管道两端的关闭是有先后顺序的,如果先关闭写端从另一端读取数据时,read函数会返回0,表示管道已经关闭。但是如果先关闭读端从另一端写入数据时,则会将写数据的进程接收到 SIGPIPE 信号,如果写的进程不对此信号处理,导致写进程终止。如果写进程处理了此信号,则写数据的write函数返回一个负值,表示管道已经关闭。看如下代码:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
	int fds[2];
	pipe(fds);
	//注释掉这部分将导致写进程被信号SIGPIPE终止,目的是屏蔽SIGPIPE信号,使进程不被终止
	sigset_t setSig; 			//设置信号集
	sigemptyset(&setSig);		//将信号集清空,初始化信号集
	sigaddset(&setSig,SIGPIPE);	//将SIGPIPE信号添加到信号集
	sigprocmask(SIG_BLOCK,&setSig,NULL);	//将setSig信号集中的信号加入信号掩码中,作为新的信号屏蔽字
	
	char szBuf[10] = {0};
	if(fork() == 0){  		//子进程
		close(fds[1]);		//子进程关闭写
		sleep(2);			//确保父关闭读的时间,并且写入管道中
		if(read(fds[0], szBuf, sizeof(szBuf)))	//读取管道中的内容
			puts(szBuf);
		close(fds[0]);		//子进程关闭读
	}
	else{
		close(fds[0]);//父进程关闭读
		write(fds[1], "hello", 6);	//父进程通过fds[1]向管道中写入hello
		wait(NULL);			//等待子进程结束
		write(fds[1], "world", 6);	//子进程已经关闭了,父进程读不到东西了
		close(fds[1]);				//父进程关闭读
	}
	return 0;
}

3 命名管道(FIFO)

  • 上一节讲了无名管道只能在亲缘关系的进程中通信,很大程度上限制了管道的使用。命名管道可以突破这个限制,通过指定管道文件的路径实现不相关进程之间的通信。实际上,使用管道通信的操作,在Linux 练习四 (目录操作函数 + 文件操作函数)中就有提及,还实现了进程通信的功能。

3.1 创建删除管道文件

创建FIFO文件的方式和创建普通文件的方式一样,其函数名和 Linux下创建FIFO的命令名一样。
删除FIFO文件和 Linux下命令也一样。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);	//创建管道文件
int unlink(const char *pathname);				//删除管道文件

参数 pathname 为要创建的 FIFO 文件的全路径名;
参数 mode为文件的访问权限
如果创建成功,则返回 0,否则-1。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])//演示通过命令行传递参数
{
	if(argc != 2){	//检查参数数量
		puts("Usage: MkFifo.exe {filename}");
		return -1;
	}
	if(mkfifo(argv[1], 0666) == -1){ //创建一个管道文件
		perror("mkfifo fail");
		return -2;
	}
	//删除管道文件
	unlink(argv[1]);
	return 0;
} 

还可以使用命令创建和删除FIFO文件使用两个终端完成,必须一边读一边写,否则会卡住。

  • 使用命令mkfifo创建管道文件,不能重复创建同一个管道文件
  • 可以使用unlink删除管道文件
  • 通过cat命令和echo命令和输入输出指向>和<来读写管道文件的案例,注意不要使用vim打开管道文件。
    在这里插入图片描述

3.2 打开和关闭FIFO文件

  • 对 FIFO 类型的文件的打开/关闭跟普通文件一样,都是使用 open 和 close 函数。如果打开时使用O_WRONLY 选项,则打开 FIFO 的写入端,如果使用 O_RDONLY 选项,则打开FIFO 的读取端,写入端和读取端都可以被几个进程同时打开。在Linux 练习四 (目录操作函数 + 文件操作函数)中2.10 管道中有提及。
  • 如果以读取方式打开 FIFO,并且还没有其它进程以写入方式打开 FIFO,open 函数将被阻塞;同样,如果以写入方式打开 FIFO,并且还没其它进程以读取方式 FIFO,open 函数也将被阻塞。
  • 与 PIPE 相同,关闭 FIFO 时,如果先关读取端,将导致继续往 FIFO 中写数据的进程接收 SIGPIPE 的信号

3.3 管道案例:基于管道的客服端服务器程序

  • 服务器端:
    维护服务器管道,接受来自客户端发来的字符串,将小写字母转换为大写字母,然后通过每个客户端维护的管道发给客户端。
//客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<ctype.h>
//定义客户端数据结构体
typedef struct tagmag
{
	int client_pid;
	char my_data[512];
}MSG;

int main()
{
	int server_fifo_fd,client_fifo_fd;	//定义客户端管道描述符和用户端管道描述符
	char client_fifo[256];				//设置客户端缓冲区
	MSG my_msg;		
	char* pstr;
	memset(&my_msg,0,sizeof(MSG));		//清空my_msg
	mkfifo("SERVER_FIFO",0777);			//新建一个管道文件,权限是0777
	server_fifo_fd = open("./SERVER_FIFO",O_RDONLY);	//以只读的方式打开管道文件
	if(server_fifo_fd == -1){	//打开失败的处理
		perror("server_fifo_fd");
		exit(-1);
	}
	int iret;
	//读取管道文件不为空的情况,将管道内容读到结构体的my_data中
	while((iret = read(server_fifo_fd,&my_msg.my_data,sizeof(MSG))>0)){ 
		pstr = my_msg.my_data;		
		printf("%s\n",my_msg.my_data);		//打印客户端数据
		while(*pstr!='\0'){					//将所有字符转为大写字符
			*pstr = toupper(*pstr);
			pstr++;	
		}
		memset(client_fifo,0,256);	//清空管道文件
		sprintf(client_fifo,"CLIENT_FIFO_%d",my_msg.client_pid);//客户端pid格式化写入client_fifo中
		client_fifo_fd = open(client_fifo,O_WRONLY);//客户端以只写的方式打开client_fifo命名的文件客户管道
		if(client_fifo_fd == -1){
			perror("client_fifo_fd");
			exit(-1);
		}
		write(client_fifo_fd,&my_msg,sizeof(MSG)); //将结构体写入管道内容
		printf("%s\n",my_msg.my_data);
		printf("OVER!\n");
		close(client_fifo_fd);
	}
	return 0;
}
  • 客户端:
    想服务器端发送数据,然后从自己的客户端管道中接受服务器返回的数据。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
//定义客户端数据结构体
typedef struct tagmag
{
	int client_pid;
	char my_data[512];
}MSG;

int main()
{
	int server_fifo_fd,client_fifo_fd;
	char client_fifo[256] = {0};
	sprintf(client_fifo,"CLIENT_FIFO_%d",getpid());//将客户端id写入client_fifo字符串中
	MSG my_msg;
	memset(&my_msg,0,sizeof(MSG));	//清空结构体
	my_msg.client_pid = getpid();	//获取客户端的进程id
	server_fifo_fd = open("./SERVER_FIFO_NAME",O_WRONLY);	//以只写的方式打开服务端管道文件,并获取文件描述符
	mkfifo(client_fifo,0777); //以client_fifo的内容,创建属于该进程的管道文件
	while(1){
		int n = read(STDIN_FILENO,my_msg.my_data,512);//从标准输入读入字符串到my_data
		my_msg.my_data[n] = '\0';
		write(server_fifo_fd,&my_msg,sizeof(MSG));//将结构体内容写入服务器管道文件中
		client_fifo_fd = open(client_fifo,O_RDONLY);//以只读的方式打开客户端管道文件,并且获取文件描述符
		n = read(client_fifo_fd,&my_msg,sizeof(MSG));//将结构体读入客户端管道文件中
		my_msg.my_data[n] = 0;	
		write(STDOUT_FILENO,my_msg.my_data,strlen(my_msg.my_data));//将my_data内容写入标准输入输出中
		close(client_fifo_fd);//关闭客户端
	}
	unlink(client_fifo);//删除客户端管道文件
	return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/395097.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】STL——string类的模拟实现

文章目录&#x1f449;string类&#x1f448;&#x1f4d5; 概念&#x1f4d5; 成员变量&#x1f4d5; 构造函数、析构函数&#x1f4d5; size() 、getstr() 函数&#x1f4d5; 拷贝构造&#x1f4d5; 赋值重载&#x1f4d5; 迭代器&#x1f4d5; 运算符重载&#x1f4d5; 尾插…

如何管控第三方软件,保护企业数据安全?

日前&#xff0c;密码管理供应商LastPass公布了关于其数据泄露事件的调查新进展。据其透露&#xff0c;这是一起“二次协同攻击”事件。LastPass在2022年8月、12月先后披露的两起违规事件&#xff0c;这两起事件的攻击链有关联。在此次攻击事件中&#xff0c;LastPass发现恶意黑…

王道计算机组成原理课代表 - 考研计算机 第五章 中央处理器 究极精华总结笔记

本篇博客是考研期间学习王道课程 传送门 的笔记&#xff0c;以及一整年里对 计算机组成 知识点的理解的总结。希望对新一届的计算机考研人提供帮助&#xff01;&#xff01;&#xff01; 关于对 “中央处理器” 章节知识点总结的十分全面&#xff0c;涵括了《计算机组成原理》课…

C++基础——C++相比C语言的新特性梳理总结(C++新特性、输入输出方式、命名空间namespace)

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…

论文翻译 | Momentum Contrast for Unsupervised Visual Representation Learning(前三章)

前言&#xff1a; 上一次读恺明大神的文章还是两年前&#xff0c;被ResNet的设计折服得不行&#xff0c;两年过去了&#xff0c;我已经被卷死在沙滩上 Momentum Contrast for Unsupervised Visual Representation Learning 摘要 我们提出了针对无监督表征学习的方法MOCO,利用…

上门按摩预约APP源码-东郊到家源码(开发,PHP,平台搭建)

一、什么是上门按摩预约APP源码&#xff1f; 上门按摩预约APP源码是一款家政服务类型的APP&#xff0c;可以帮忙用户在家就能享受按摩的服务。APP源码分两端&#xff0c;一端是用户端&#xff0c;另外一端是技师端。采用的技术&#xff0c;前端是安卓IOS&#xff0c;后端是PHP&…

java_Day004

1.二维数组 二维数组的创建与初始化&#xff08;java是支持规则数组和不规则数组的&#xff09; 例&#xff1a;int[][] array {{1,2},{2,3}{3,4,5}}; 结构如下&#xff1a; 二维数组的遍历&#xff1a; 例子&#xff1a; Testpublic void test21(){int[][] array new int[…

C++学习记录——십이 vector

文章目录1、vector介绍和使用2、vector模拟实现insert和erase和迭代器失效补齐其他函数深浅拷贝难点思考1、vector介绍和使用 vector可以管理任意类型的数组&#xff0c;是一个表示可变大小数组的序列容器。 通过vector文档来看它的使用。 #include <iostream> #inclu…

集群、分布式的理解

一、单机模式小型系统相对简单&#xff0c;所有的业务全部写在一个项目中&#xff0c;部署服务到一台服务器上&#xff0c;所有的请求业务都由这台服务器处理&#xff0c;这就是单机模式。显然&#xff0c;当业务增长到一定程度的时候&#xff0c;服务器的硬件会无法满足业务需…

强化学习 | 课堂笔记 | 第三课 MP的便利性,随机逼近方法

一、回顾 一、值函数、贝尔曼方程、贝尔曼最优方程 二、最优值函数 三、ADP 3.1 VI 3.2 PI 四、ADP可以使用的条件 五、Q函数 六、解决问题的方案 &#xff08;指的是解决“四 ADP可以使用的条件”中的三个问题&#xff09; 二、期望的计算 一、Markov过程的便利性 1…

新搭建Gitlab代码仓代码如何导入

这里写目录标题一级目录1.本地代码如何导入新Gitlab2.怎么将旧Gitlab代码导入新Gitlab一级目录 1.本地代码如何导入新Gitlab 修改本地代码 .git 目录下面的config 文件&#xff0c;主要是url参数&#xff0c;将url指向新的Gitlab仓库地址 [core]repositoryformatversion 0f…

【1096. 花括号展开 II】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 如果你熟悉 Shell 编程&#xff0c;那么一定了解过花括号展开&#xff0c;它可以用来生成任意字符串。 花括号展开的表达式可以看作一个由 花括号、逗号 和 小写英文字母 组成的字符串&#xff0c;定…

E900V21C(S905L-armbian)安装armbian-Ubuntu(WiFi)

基本上是s905L芯片的刷机都是如此&#xff0c;包括Q7等 在网上寻找好多的教程关于e900v21c的刷机包和教程都少的可怜&#xff0c;唯一的就是这个&#xff1a;山东联通版创维E900V21C盒子刷入Armbiam并安装宝塔和Docker&#xff0c;但他是不能用WiFi和蓝牙的然后就是寻找s90l的…

C++基础了解-01-基础语法

基础语法 一、基础语法 C 程序可以定义为对象的集合&#xff0c;这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象&#xff0c;方法、即时变量。 对象 - 对象具有状态和行为。例如&#xff1a;一只狗的状态 - 颜色、名称、品种&#xff0c;行为 -…

【LeetCode每日一题】——334.递增的三元子序列

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【题目进阶】九【时间频度】十【代码实现】十一【提交结果】一【题目类别】 贪心算法 二【题目难度】 中等 三【题目编号】 334.递增的三元子序列 四【题…

Vue3视频播放组件(Video)

Vue2视频播放组件 可自定义设置以下属性&#xff1a; 视频文件url&#xff08;videoUrl&#xff09;&#xff0c;必传&#xff0c;支持网络地址https和相对地址 视频封面url&#xff08;videoCover&#xff09;&#xff0c;默认为null&#xff0c;支持网络地址https和相对地…

【nacos2.2.1本地启动】

nacos2.2.1本地启动填坑之行 下载nacos代码 nacos文档地址&#xff1a;https://nacos.io/zh-cn/docs/quick-start-spring.html github地址下载代码&#xff1a;https://github.com/alibaba/nacos.git appllo文章&#xff1a;https://blog.51cto.com/muxiaonong/3933418 下…

UEFI学习(三)-创建一个dxe driver-UDK2017

创建一个dxe driver 创建UEFI DXE driver DXE驱动的运行阶段 DXE驱动创建 创建UEFI DXE driver 在edk2中&#xff0c;我们可以了解到它有非常多种类的模块&#xff0c;每种模块运行于不同阶段&#xff0c;上一阶段&#xff0c;我们尝试了一下标准应用程序的工程模块&#xff0c…

Centos7超详细安装教程

Centos 7适合初入门的带图形化的界面系统安装 本文是基于VMware虚拟机&#xff0c;centos7 64位安装教学 文章目录Centos 7适合初入门的带图形化的界面系统安装一、软件准备二、VMware新建适配虚拟机三、Centos 安装四、基础检查一、软件准备 VMware 虚拟机安装 官网下载链接&…

Redis 做延迟消息队列

背景 看到消息队列&#xff0c;我们肯定会想到各种MQ&#xff0c;比如&#xff1a;RabbitMQ&#xff0c;acivityMQ、RocketMQ、Kafka等。 但是&#xff0c;当我们需要使用消息中间件的时候&#xff0c;并非每次都需要非常专业的消息中间件&#xff0c;假如我们只有一个消息队…