Linux内核编程 匿名管道pipe、命名管道fifo 使用案例

目录

一:IPC进程间通信

二:shell中使用管道

三:管道的特点

四:pipe函数

五:匿名管道pipe

六:共用解决方案

七:命名管道fifo

八:实现两个窗口互相聊天

九:使用管道 模拟聊天系统


一:IPC进程间通信

什么是管道?

管道是Unix中最古老的进程间通信的形式

我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

我们通常把是把一个进程的输出连接或“管接”(经过管道来连接)到另一个进程的输入

在前面学习到的IPC技术之信号并不适合用于数据传输,信号只能传输int类型的数据(传输数据量少) ,同时使用信号还存在信号屏蔽和信号冲突的问题

二:shell中使用管道

shell所做的工作从最终效果上看是这样的:

重新安排标准输入和输出流之间的连接,使数据从键盘输入流过两个命令再输出到屏幕

三:管道的特点

管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道 (对于管道,数据只能进行单向的传输)

管道分为匿名管道PIPE和命名管道FIFO

匿名管道:用于父子进程或者兄弟进程之间(具有亲缘关系的进程)进行通信

通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道

四:pipe函数

包含头文件<unistd.h>

功能:创建一无名(匿名)管道

函数原型

int pipe(int file_descriptor[2]);

参数

file_descriptor:文件描述符数组

其中file_descriptor[0]表示读端,file_descriptor[1]表示写端

返回值:成功返回0,失败返回错误代码

管道 半双工 通过fd控制管道流向,并且流向确定后是不可以修改的

图示如下:

读入写出 

管道的创建没有方向,但是在操作完fd之后流向也就产生了

五:匿名管道pipe

查看fork使用说明 

查看pipe使用说明 

匿名管道pipe 使用示例

#include<iostream>
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<string.h>

using namespace std;

int main()
{
	int fdarr[2] = { 0 };
	char buf[50] = { 0 };
	char resbuf[50] = { 0 };
	pid_t pid = 0;
	if (pipe(fdarr) != 0)
	{
		perror("pipe error");
	}
	else
	{
		pid = fork();
		if (pid > 0)
		{
			//管道的读端关闭,执行写
			close(fdarr[0]);

			while (1)
			{
				fgets(buf, sizeof(buf), stdin);
				int wres = write(fdarr[1], buf, sizeof(buf));
				bzero(buf, sizeof(buf));
			}
		}
		else if(pid == 0)
		{
			//管道的写端关闭,执行读
			close(fdarr[1]);

			while (1)
			{
				int res = read(fdarr[0], resbuf, sizeof(resbuf));
				cout << "res = " << res << "resbuf = " << resbuf << endl;
				bzero(resbuf, sizeof(resbuf));
			}
		}
	}
	
	return 0;
}

结果:自己发送,自己接收,没有那种数据传递的效果(感觉上只是单纯的文件读写)

查看一下具体收发数据:

if (pid > 0)
		{
			//管道的读端关闭,执行写
			close(fdarr[0]);

			while (1)
			{
				fgets(buf, sizeof(buf), stdin);
				int wres = write(fdarr[1], buf, sizeof(buf));
				cout << "pid = " << getpid() << "通过管道发送数据" << endl;
				bzero(buf, sizeof(buf));
			}
		}
		else if(pid == 0)
		{
			//管道的写端关闭,执行读
			close(fdarr[1]);

			while (1)
			{
				int res = read(fdarr[0], resbuf, sizeof(resbuf));
				cout << "pid = " << getpid() << "从管道读取数据 res = " << res << "resbuf = " << resbuf << endl;
				bzero(resbuf, sizeof(resbuf));
			}
		}

结果:

不难看出,只能进程号80424的进程发送数据,进程号80425的进程接收数据,单向通信

可以详细查看一下收发数据,看出这种单向通信明显效果不好,需要改进为双向通信

双向通信 使用示例:

#include<iostream>
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<string.h>

using namespace std;

int main()
{
	int fdarr[2] = { 0 };//负责 父到子 传递
	int fdarr2[2] = { 0 };//负责 子到父 传递

	char buf[50] = { 0 };
	char resbuf[50] = { 0 };
	pid_t pid = 0;
	if (pipe(fdarr) != 0 || pipe(fdarr2)!=0 )
	{
		perror("pipe error");
	}
	else
	{
		pid = fork();
		if (pid > 0)
		{
			//管道的读端关闭,执行写
			close(fdarr[0]);

			close(fdarr2[1]);

			while (1)
			{
				fgets(buf, sizeof(buf), stdin);
				int wres = write(fdarr[1], buf, sizeof(buf));
				cout << "pid = " << getpid() << "通过管道发送数据" << endl;
				bzero(buf, sizeof(buf));

				int res = read(fdarr2[0], buf, sizeof(buf));
				cout << "pid = " << getpid() << "从管道接到数据 res = " << res << "buf = " << buf << endl;
				bzero(buf, sizeof(buf));
			}
		}
		else if(pid == 0)
		{
			//管道的写端关闭,执行读
			close(fdarr[1]);

			close(fdarr2[0]);

			while (1)
			{
				int res = read(fdarr[0], resbuf, sizeof(resbuf));
				cout << "pid = " << getpid() << "从管道读取数据 res = " << res << "resbuf = " << resbuf << endl;
				bzero(resbuf, sizeof(resbuf));

				fgets(resbuf, sizeof(resbuf), stdin);
				int wres = write(fdarr2[1], resbuf, sizeof(resbuf));
				cout << "pid = " << getpid() << "通过管道发送数据" << endl;
				bzero(resbuf, sizeof(resbuf));
			}
		}
	}
	
	return 0;
}

结果:

进程号80924的进程发送数据给80925进程,同时进程号80925的进程也可以发送数据给80924的进程

看起来的聊天效果:目前是一人发一句并且还在一个窗口上,虽能够双向通信,但是还是需要改进的!

六:共用解决方案

VSCode中两个工程可以共用一个解决方案,使用示例如下

为了实现多窗口模拟聊天,需要创建两个工程,共用一个解决方案

右键解决方案 添加 新建项目 

此时添加项目,共用一个解决方案【只有一个.sln,在工程A中】 

创建完成后,可以看到两个工程是共用一个解决方案的

分别对两个项目 添加 新建项  

各自给个主入口函数命名.cpp ,如AMain命名规范(不要随意命名,养成良好习惯)

七:命名管道fifo

命名管道可以从命令行上创建,推荐的命令行方法是使用下面这个命令:

$ mkfifo filename

命名管道也可以从程序里创建,相关函数有:

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

int mknod(const char *filename,mode_t mode|S_IFIFO,(dev_t) 0);

需要对文件名是否存在先做出判断

访问一个FIFO文件:

FIFO和通过pipe调用创建的管道不同,它是一个有名字的文件而表示一个打开的文件描述符

在对它进行读或写操作之前必须先打开它

FIFO文件也要用open和close函数来打开或关闭,除了一些额外的功能外,整个操作过程与我们前面介绍的文件操作是一样的

传递给open调用的是一个FIFO文件的路径名,而不是一个正常文件的路径名

用open打开FIFO文件:

在打开FIFO文件时需要注意一个问题:

即程序不能以O_RDWR模式打开FIFO文件进行读写(管道是半双工的,要么读,要么写,不能又可读又可写)

如果确实需要在程序之间双向传递数据的话,我们可以同时使用一对FIFO或管道,一个方向配一个还可以用先关闭再重新打开FIFO的办法明确地改变数据流的方向

查看fifo使用说明 

查看access使用说明 

查看open头文件 

两个窗口,模拟聊天,代码示例如下 (一个窗口为一个进程)

AMain.cpp

#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include<stdio.h>
#include <fcntl.h>
#include<string.h>

using namespace std;

int main()
{
	int wfd = 0;
	char buf[50] = { 0 };

	umask(0);
	//先尝试访问这个路径下的这个fifo文件,检查是否可以访问成功
	if (access("/root/projects/A2B.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/A2B.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "A2B fifo 文件存在" << endl;
	}

	wfd = open("/root/projects/A2B.fifo", O_WRONLY);

	while (1)
	{
		cout << "A2B 请输入" << endl;
		fgets(buf, sizeof(buf), stdin);
		int writeRes = write(wfd, buf, sizeof(buf));
		cout << "A2B 发出 writeRes = " << writeRes << endl;
		bzero(buf, sizeof(buf));
	}
	return 0;
}

BMain.cpp

#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include<stdio.h>
#include <fcntl.h>
#include<string.h>

using namespace std;

int main()
{
	int rfd = 0;
	char buf[50] = { 0 };

	umask(0);
	//先尝试访问这个路径下的这个fifo文件,检查是否可以访问成功
	if (access("/root/projects/A2B.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/A2B.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "A2B fifo 文件存在" << endl;
	}

	rfd = open("/root/projects/A2B.fifo", O_RDONLY);

	while (1)
	{
		int readRes = read(rfd, buf, sizeof(buf));
		cout << "读到 readRes" << readRes << "buf = " << buf << endl;
		bzero(buf, sizeof(buf));
	}
	return 0;
}

打开A工程编译 

打开B工程编译 

A B工程必须同时运行,才可以使用有名管道

有名管道要读端,写端都存在,这个管道才有作用

open阻塞式函数,要读端写端同时开启才行

结果:

虽然可以实现两个窗口通信,但是目前只能实现一端发送一端接收,仍需改进!

fifo管道文件,有流经数据,但是不会保存数据

管道文件,在硬盘中以文件形式存在,但管道不管程序是否存在都不可能存放数据,管道只是一个载体,让数据能够流经的通道

可以看到fifo管道文件,但实际上这个管道文件是虚拟的,测试中删除了fifo管道文件,照样可以发送接收数据

因为真正的数据传输是在内存中操作的,和管道文件实际上没啥太大关系

命名管道缺陷:管道文件可能会被用户在操作过程中误删(不合法)

八:实现两个窗口互相聊天

实现两个窗口 发送 接收 以此来模拟聊天窗口

AMain.cpp

#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include<stdio.h>
#include <fcntl.h>
#include<string.h>

using namespace std;

int main()
{
	int wfd = 0;
	int rfd = 0;
	char buf[50] = { 0 };

	umask(0);
	//先尝试访问这个路径下的这个fifo文件,检查是否可以访问成功
	if (access("/root/projects/A2B.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/A2B.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "A2B fifo 文件存在" << endl;
	}

	if (access("/root/projects/B2A.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/B2A.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "B2A fifo 文件存在" << endl;
	}

	wfd = open("/root/projects/A2B.fifo", O_WRONLY);

	rfd = open("/root/projects/B2A.fifo", O_RDONLY);

	while (1)
	{
		cout << "A2B 请输入" << endl;
		fgets(buf, sizeof(buf), stdin);
		int writeRes = write(wfd, buf, sizeof(buf));
		cout << "A2B 发出 writeRes = " << writeRes << endl;
		bzero(buf, sizeof(buf));

		int readRes = read(rfd, buf, sizeof(buf));
		cout << "读到 readRes = " << readRes << "buf = " << buf << endl;
	}
	return 0;
}

BMain.cpp

#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include<stdio.h>
#include <fcntl.h>
#include<string.h>

using namespace std;

int main()
{
	int rfd = 0;
	int wfd = 0;
	char buf[50] = { 0 };

	umask(0);
	//先尝试访问这个路径下的这个fifo文件,检查是否可以访问成功
	if (access("/root/projects/A2B.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/A2B.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "A2B fifo 文件存在" << endl;
	}

	if (access("/root/projects/B2A.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/B2A.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "B2A fifo 文件存在" << endl;
	}

	rfd = open("/root/projects/A2B.fifo", O_RDONLY);

	wfd = open("/root/projects/B2A.fifo", O_WRONLY);

	while (1)
	{
		int readRes = read(rfd, buf, sizeof(buf));
		cout << "读到 readRes" << readRes << "buf = " << buf << endl;
		bzero(buf, sizeof(buf));

		cout << "B2A 请输入" << endl;
		fgets(buf, sizeof(buf), stdin);
		int writeRes = write(wfd, buf, sizeof(buf));
		cout << "B2A 发出 writeRes = " << writeRes << endl;
		bzero(buf, sizeof(buf));
	}
	return 0;
}

结果:

可以实现在一个窗口发出消息,在另一个窗口可以收到消息,

但是只能这边发一句,对面要立马回一句,还是有缺陷的

如果出现有一端发多次,对方需要每回一次才能接收到之前对方发的消息 

(也就是发送和接收次数需要对应,显然还是不符合那种现实中,一人可以发多次,而另外一人可以只回一次,因此还是需要改进的)

九:使用管道 模拟聊天系统

目标:实现一个人可以一次发送多条消息,对方可以只回复一句

AMain.cpp

#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include<stdio.h>
#include <fcntl.h>
#include<string.h>

using namespace std;

int main()
{
	pid_t pid = 0;
	int wfd = 0;
	int rfd = 0;
	char buf[50] = { 0 };

	umask(0);
	//先尝试访问这个路径下的这个fifo文件,检查是否可以访问成功
	if (access("/root/projects/A2B.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/A2B.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "A2B fifo 文件存在" << endl;
	}

	if (access("/root/projects/B2A.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/B2A.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "B2A fifo 文件存在" << endl;
	}

	pid = fork();

	if (pid > 0)
	{
		wfd = open("/root/projects/A2B.fifo", O_WRONLY);
		while (1)
		{
			fgets(buf, sizeof(buf), stdin);
			int writeRes = write(wfd, buf, sizeof(buf));
			bzero(buf, sizeof(buf));
		}
	}
	else if (pid == 0)
	{
		rfd = open("/root/projects/B2A.fifo", O_RDONLY);
		while (1)
		{
			int readRes = read(rfd, buf, sizeof(buf));
			cout << "B进程说: "  << buf << endl;
			bzero(buf, sizeof(buf));
		}
	}
	return 0;
}

BMain.cpp

#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include<stdio.h>
#include <fcntl.h>
#include<string.h>

using namespace std;

int main()
{
	pid_t pid = 0;
	int rfd = 0;
	int wfd = 0;
	char buf[50] = { 0 };

	umask(0);
	//先尝试访问这个路径下的这个fifo文件,检查是否可以访问成功
	if (access("/root/projects/A2B.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/A2B.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "A2B fifo 文件存在" << endl;
	}

	if (access("/root/projects/B2A.fifo", F_OK) == -1)
	{
		//不成功 则表示文件路径不存在
		if (mkfifo("/root/projects/B2A.fifo", 0777) == -1)
		{
			perror("mkfifo error");
		}
	}
	else
	{
		cout << "B2A fifo 文件存在" << endl;
	}

	pid = fork();

	if (pid > 0)
	{
		wfd = open("/root/projects/B2A.fifo", O_WRONLY);
		while (1)
		{
			fgets(buf, sizeof(buf), stdin);
			int writeRes = write(wfd, buf, sizeof(buf));
			bzero(buf, sizeof(buf));
		}
	}
	else if (pid == 0)
	{
		rfd = open("/root/projects/A2B.fifo", O_RDONLY);
		while (1)
		{
			int readRes = read(rfd, buf, sizeof(buf));
			cout << "A进程说" << buf << endl;
			bzero(buf, sizeof(buf));
		}
	}
	return 0;
}

结果:

在一个窗口中可以发送多次消息,同时不会影响另一个窗口的消息接收

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chenruhan_QAQ_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值