Linux进程间通信(一)--- 管道

进程间通信

不同进程有不同的用户地址空间,进程之间的数据都是相互独立的,即使是父子进程,也就是说在父进程中的全局变量,即使在子进程中对其进行了修改,也不会影响到父进程中该变量的值,两个进程的地址空间不同。

进程之间如需交换数据,必须通过内核。在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC)

管道

管道是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开的文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。

管道分为无名管道和有名管道,分别进行解析。

一、无名管道

主要用于父进程与子进程之间,或者两个兄弟进程之间。在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。一般情况下,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信。

当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。这种传输遵循“先入先出”(FIFO)的规则。

向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。

1、使用
  • 头文件:#include <unistd.h>
  • 函数原型:int pipe(pipefd[2])
  • 若执行成功,pipefd内将存储两个文件描述符,指向管道的两端
  • pipefd[0]指向读端,pipefd[1]指向写端
  • 若执行失败,返回值为-1。
    在这里插入图片描述
2、演示程序
2.1 父进程向子进程发送数据

写测试程序test-1,再fork子进程,父子进程建立无名通道,父进程向子进程发送数据

//test-1
#include <stdio.h>
#include <unistd.h> //pipe通信所需头文件
#include <stdlib.h>
#include <string.h>//memset函数所需
#define BUFSIZE 20
int main()
{
	int fd[2];
	pid_t pid;
	char readBuf[BUFSIZE];
	char writeBuf[BUFSIZE]="jisuanjiwangluo";
	memset(readBuf,0,BUFSIZE);//接受数据的数组清零
	if(pipe(fd)==-1)//pipe建立失败
	{
		perror("create pipe fail!");
		exit(-1);
	}
	if((pid=fork())==-1)//fork子进程失败
	{
		perror("create child fail!");
		exit(-1);
	}
	else if(pid==0)//子进程
	{
		//子进程读管道数据
		printf("Sub-process read data from pipe:");
		if((read(fd[0],readBuf,BUFSIZE))==-1)//子进程读管道数据失败
		{
			perror("Sub-process read fail!");
			exit(-1);
		}
		printf("%s\n",readBuf);
		exit(0);
	}
	else if(pid>0)//父进程
	{
		//父进程写数据到管道
		printf("Father-process write date to pipe:");
		if((write(fd[1],writeBuf,BUFSIZE))==-1)
		{
			perror("Father-process write fail!");
			//exit(-1);
		}
		else
		{
			printf("%s\n",writeBuf);
		}
		sleep(2);//等待子进程结束
	}
	return 1;
}

该测试需要注意的是父进程需要等待子进程。简单为了测试,随手在父进程函数块的最后添加了sleep(2)语句,默认子进程在2s内能结束进程并退出。但正确的做法应该是调用wait函数,捕捉子进程退出的信号后父进程再向后执行

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

2.2 子进程向父进程发送数据

测试程序test-2,再fork子进程,父子进程建立无名通道,子进程向父进程发送数据

//test-2
#include <stdio.h>
#include <unistd.h> //pipe通信所需头文件
#include <stdlib.h>
#include <string.h>//memset函数所需
#define BUFSIZE 20
int main()
{
	int fd[2];
	pid_t pid;
	char readBuf[BUFSIZE];
	char writeBuf[BUFSIZE]="jisuanjiwangluo";
	memset(readBuf,0,BUFSIZE);//接受数据的数组清零
	if(pipe(fd)==-1)//pipe建立失败
	{
		perror("create pipe fail!");
		exit(-1);
	}
	if((pid=fork())==-1)//fork子进程失败
	{
		perror("create child fail!");
		exit(-1);
	}
	else if(pid==0)//子进程
	{
		//子进程写管道数据
		printf("Sub-process write data to pipe:");
		if((write(fd[1],writeBuf,BUFSIZE))==-1)//子进程写管道数据失败
		{
			perror("Sub-process write fail!");
			exit(-1);
		}
		printf("%s\n",writeBuf);
		exit(0);
	}
	else if(pid>0)//父进程
	{
		//sleep(2);//等待子进程写管道
		//父进程读管道的数据
		printf("Father-process read date from pipe:");
		if((read(fd[0],reatBuf,BUFSIZE))==-1)
		{
			perror("Father-process read fail!");
			//exit(-1);
		}
		else
		{
			printf("%s\n",readBuf);
		}
		sleep(2);//等待子进程结束
	}
	return 1;
}

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

2.3 双向通信

若要实现双向传递,则需要建立两条无名通道fd1,fd2,分别实现子进程向父进程传递数据,父进程向子进程传递数据。

//test-1
#include <stdio.h>
#include <unistd.h> //pipe通信所需头文件
#include <stdlib.h>
#include <string.h>//memset函数所需
#define BUFSIZE 20
int main()
{
	int fd1[2],fd2[2];//建立起两条管道
	pid_t pid;
	char readBuf[BUFSIZE];//如果通信成功,该数字组应是"i am your son"
	char writeBuf[BUFSIZE]="i am your father";

	char subReadBuf[BUFSIZE];//如果通信成功,该数组应是“i am your father”
	char subWriteBuf[BUFSIZE]="i am your son";

	memset(readBuf,0,BUFSIZE);//接受数据的数组清零

	if(pipe(fd1)==-1 | pipe(fd2)==-1)//pipe建立失败
	{
		perror("create pipe fail!");
		exit(-1);
	}
	if((pid=fork())==-1)//fork子进程失败
	{
		perror("create child fail!");
		exit(-1);
	}
	else if(pid==0)//子进程
	{
		//子进程先写管道数据1,再读管道2
		printf("Sub-process write data to pipe:");
		if((write(fd1[1],subWriteBuf,BUFSIZE))==-1)//子进程写管道数据失败
		{
			perror("Sub-process write fail!");
			exit(-1);
		}
		printf("%s\n\n",subWriteBuf);
		//close(fd1[1]);//关闭管道1的写端

		printf("Sub-process read data from pipe:");
		if((read(fd2[0],subReadBuf,BUFSIZE))==-1)
		{
			perror("Sub-process read fail!");
			exit(-1);
		}
		else
		{
			printf("%s\n\n",subReadBuf);
		}
		exit(0);
	}
	else if(pid>0)//父进程
	{
	
		//父进程先读管道1的数据,再写管道2
		printf("Father-process read date from pipe:");
		if((read(fd1[0],readBuf,BUFSIZE))==-1)
		{
			perror("Father-process read fail!");
			//exit(-1);
		}
		else
		{
			printf("%s\n\n",readBuf);
		}
		//close(fd1[0]);

		printf("Father-process write data to pipe:");
		if((write(fd2[1],writeBuf,BUFSIZE))==-1)
		{
			perror("Father-process write fail!");
		}
		else
		{
			printf("%s\n\n",writeBuf);
		}
		sleep(2);//等待子进程结束
	}
	return 1;
}

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

二、有名管道

有名管道(命名管道)是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件。任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。因此,实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。

强调,与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。

值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。

1、使用
  1. 头文件:#include <unistd.h>
  2. 函数原型:int mkfifo(const char *pathname,mode_t mode);
    其中,pathname是FIFO文件名称;mode是文件访问权限;成功则返回0,失败返回-1,并设置erron。
  3. 创建删除:用命令mkfifo创建,不能重复创建,用命令unlink删除
2、演示程序
2.1 父子进程间通信—父进程向子进程发送数据

测试程序test2-1,在fork子进程,父子进程间建立有名管道,父进程向子进程发送数据

//test2-1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>//用于文件读写
#include <errno.h>
#define BUFSIZE 20
#define PATH_NAME "fifo_file"
int main()
{
	pid_t pid;
	char writeBuf[BUFSIZE]="I am your father";
	char readBuf[BUFSIZE];
	memset(readBuf,0,BUFSIZE);
	int fd;

	if(mkfifo(PATH_NAME,0666)==-1 && erron!=EEXIST)
	{
		perror("Create fifo fail!");
		exit(-1);
	}
	if((pid=fork())==-1)//创建子进程失败
	{
		perror("Create sub-process fail!");
		exit(-1);
	}
	else if(pid==0)//子进程读取管道数据
	{
		fd=open(PATH_NAME,O_RDONLY,0666);
		if(fd<0)//打开失败
		{
			perror("open FIFO_FILE fail!");
			exit(-1);
		}
		if((read(fd,readBuf,BUFSIZE))<0)
		{
			perror("sub-process read fail!");
			exit(-1);
		}
		printf("[Sub-process read data from Fifo] : %s\n",readBuf);
		close(fd);
	}
	else if(pid>0)//父进程向管道写数据
	{
		fd=open(PATH_NAME,O_WRONLY,0666);
		if(fd<0)//打开失败
		{
			perror("open FIFO_FILE fail!");
			exit(-1);
		}
		if((write(fd,writeBuf,BUFSIZE))<0)
		{
			perror("father-process write fail!");
			exit(-1);
		}
		printf("[Father-process write data to Fifo] : %s\n",writeBuf);
		close(fd);
		sleep(2);
	}
	return 1;
}
2.2 父子进程间双向通信

由于管道是单向通信,无法直接完成双向通信。但可以通过其他方法来实现:

  • 方法一:使用一对FIFO或管道,一个方向使用一个
  • 方法二:先关闭在重新打开FIFO,改变数据流的方向
    使用时通常先是创建一个管道,再调用fork()函数创建一个子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应关系如下图在这里插入图片描述
    父子进程分别拥有自己的读写通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可
2.3 不相关进程间通信

测试程序test2-3-1/test2-3-2,两个进程间建立有名管道,test2-3-1向test2-3-2发送数据

//test2-3-1
//向test2-3-2发送数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>//用于文件读写
#include <errno.h>
#define BUFSIZE 20
#define PATH_NAME "fifo_file"
int main()
{
	char writeBuf[BUFSIZE]="Hello! Test2-3-2!";
	int fd;

	if(mkfifo(PATH_NAME,0666)==-1 && errno!=EEXIST)//如果不是已存在文件而创建失败,则异常退出
	{
		perror("Create fifo fail!");
		exit(-1);
	}
	

	fd=open(PATH_NAME,O_WRONLY,0666);
	if(fd<0)//打开失败
	{
		perror("open FIFO_FILE fail!");
		exit(-1);
	}
	if((write(fd,writeBuf,BUFSIZE))<0)
	{
		perror("test2-3-1 write fail!");
		exit(-1);
	}
	printf("[test2-3-1 write:] : %s\n",writeBuf);
	close(fd);
	return 1;
}
//test2-3-2
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>//用于文件读写
#include <errno.h>
#define BUFSIZE 20
#define PATH_NAME "fifo_file"
int main()
{
	char readBuf[BUFSIZE];
	memset(readBuf,0,BUFSIZE);
	int fd;

	if(mkfifo(PATH_NAME,0666)==-1 && errno!=EEXIST)
	{
		perror("Create fifo fail!");
		exit(-1);
	}
	fd=open(PATH_NAME,O_RDONLY,0666);
	if(fd<0)//打开失败
	{
		perror("open FIFO_FILE fail!");
		exit(-1);
	}
	if((read(fd,readBuf,BUFSIZE))<0)
	{
		perror("test2-3-2 read fail!");
		exit(-1);
	}
	printf("test2-3-2 read] : %s\n",readBuf);
	close(fd);
	unlink(PATH_NAME);
	return 1;
}
2.4 不相关进程间双向通信

在不相关进程之间的双向通信,使用一对FIFO或管道,一个方向使用一个。

//test-2-4-1
#include <stdio.h>
#include <unistd.h> //pipe通信所需头文件
#include <stdlib.h>
#include <string.h>//memset函数所需
#include <fcntl.h>//文件读写
#include <errno.h>//ERRNO
#define BUFSIZE 20
#define PATH_NAME_1 "fifo_file1"
#define PATH_NAME_2 "fifo_file2"

int main()
{
	int fd1,fd2;
	char readBuf[BUFSIZE];//如果通信成功,该数字组应是"i am your son"
	char writeBuf[BUFSIZE]="i am your test2-4-1";

	memset(readBuf,0,BUFSIZE);//接受数据的数组清零

	if((mkfifo(PATH_NAME_1,0666)==-1 | mkfifo(PATH_NAME_2,0666)==-1)&&errno!=EEXIST)//不是因为文件存在而建立失败,则异常退出
	{
		perror("create pipe fail!");
		exit(-1);
	}

	//先写再读
	printf("test2-4-1 write :");
	if((fd1=open(PATH_NAME_1,O_WRONLY,0666))==-1)
	{
		perror("fifo_file1 open fail!");
		exit(-1);
	}
	if(write(fd1,writeBuf,BUFSIZE)==-1)
	{
		perror("test2-4-1 write fail!");
		exit(-1);
	}
	printf("%s\n\n",writeBuf);
	
	printf("test2-4-1 read :");
	if((fd2=open(PATH_NAME_2,O_RDONLY,0666))==-1)
	{
		perror("fifo_file2 open fail!");
		exit(-1);
	}
	if((read(fd2,readBuf,BUFSIZE))==-1)
	{
		perror("test2-4-1 read fail!");
		exit(-1);
	}
	else
	{
		printf("%s\n\n",readBuf);
	}
	close(fd1);
	close(fd2);
	return 1;
	
}
//test2-4-2
#include <stdio.h>
#include <unistd.h> //pipe通信所需头文件
#include <stdlib.h>
#include <string.h>//memset函数所需
#include <fcntl.h>//文件读写
#include <errno.h>//ERRNO
#define BUFSIZE 20
#define PATH_NAME_1 "fifo_file1"
#define PATH_NAME_2 "fifo_file2"

int main()
{
	int fd1,fd2;
	int flag=1;
	char readBuf[BUFSIZE];//如果通信成功,该数字组应是"i am your son"
	char writeBuf[BUFSIZE]="i am your test2-4-2";

	memset(readBuf,0,BUFSIZE);//接受数据的数组清零
	if((mkfifo(PATH_NAME_1,0666)==-1 | mkfifo(PATH_NAME_2,0666)==-1)&&errno!=EEXIST)//不是因为文件存在而建立失败,则异常退出
	{
		perror("create pipe fail!");
		exit(-1);
	}
	
	//进程先写管道数据1,再读管道2
		
	printf("test2-4-2 read :");
	if((fd1=open(PATH_NAME_1,O_RDONLY,0666))==-1)
	{
		perror("fifo_file1 open fail!");
		exit(-1);
	}
	if((read(fd1,readBuf,BUFSIZE))==-1)
	{
		perror("test2-4-2 read fail!");
		exit(-1);
	}
	else
	{
		printf("%s\n\n",readBuf);
	}


	printf("test2-4-2 write :");
	if((fd2=open(PATH_NAME_2,O_WRONLY,0666))==-1)
	{
		perror("fifo_file2 open fail!");
		exit(-1);
	}
	if(write(fd2,writeBuf,BUFSIZE)==-1)
	{
		perror("test2-4-2 write fail!");
		exit(-1);
	}

	printf("%s\n\n",writeBuf);
	close(fd1);
	close(fd2);
	unlink(PATH_NAME_1);
	unlink(PATH_NAME_2);
	return 1;
}
全局补充

不可否认,管道通信的速度是比较快的,但是也有诸多限制与不便。
使用管道时需要注意以下几点:
1、管道写端有效的前提是读端有效。也就是说,如果读端关闭了,写端会收到信号SIGPIPE,通常直接导致进程终止。
2、管道有容量大小限制,当缓冲区满时写端阻塞,容量大小根据系统默认值为4k,即一页的大小,可以用ulimit –p查看
在这里插入图片描述
3、管道传送的数据类型都是无格式字节流,只需管道的两端事先约定好数据格式即可

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值