进程间通信——管道

时间:2021-12-26 19:08:05

进程间通信——管道

 要学习进程间通信方式之一的管道,就要了解进程间通信的相关概念。
进程间通信的本质:进程间通信的本质就是让不同的进程看到公共的资源
什么叫做进程间通信呢?
 进程之间要交换数据,必须要通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核的缓冲区,进程2再从 内核的缓冲区把数据拷走,内核提供的这种机制叫做:进程间通信
进程间通信的目的:
   ①数据的传输:一个进程要把数据传送给另外一个进程
  ②资源共享:多个进程之间要共享一份资源
 ③通知事件:一个进程要通知另外一个进程,告诉它发生了什么事情
④控制进程:有些进程需要完全控制另外一个进程
 介绍完进程间通信,现在就要开始学习进程间通信的方式之一:管道
什么是管道?我们把从一个进程连接到另外一个进程的数据流叫做管道。
 管道有两种分类:匿名管道和命名管道,我们先来学习匿名管道。
匿名管道:
那我们要如何创建一个匿名管道,在Linux下,我们创建管道的函数是:pipe
int pipe(int pipefd[2]);
参数:大小为2的文件描述符数组   返回值:创建成功返回0,失败返回错误代码
现在我们在父子进程中创建一个管道,其原理如下图所示:
进程间通信——管道
上面就是创建匿名管道的原理图,现在给出代码:
#include<string.h>
#include<stdio.h>
#include<unistd.h>

int main(){
	int fds[2];
	if(pipe(fds)<0){
		perror("perror");
		return 1;
	}
	pid_t id = fork();
	if(id == 0)//child->read
	{
		close(fds[1]);//子进程关闭写端
		char buff[1024];
		while(1){
			size_t size = read(fds[0],buff,sizeof(buff)-1);
			if(size >0)
			{
				buff[size] = '\0';
				printf("hello father,i am child:%s\n",buff);
			}
		}
	}
	else{//father->write
		close(fds[0]);//父进程关闭读端
		const char* msg = "hello child,i am father,hello pipe\n";
		while(1){
			write(fds[1],msg,strlen(msg));
			sleep(1);
		}
	}
	return 0;
}
由以上,我们可以得到匿名管道的特点:
①单向通信
②有血缘关系的进程才能通过管道进行相互通信,通常用于父子进程
③管道自带同步机制
④管道是面向字节流的通信服务
⑤与之相关的进程关闭的时候,管道也会随之关闭,我们把管道的生命周期叫做:“随进程”

命名管道:
 在学习命名管道之前,我们再补充几个相关概念。
①我们知道管道的本质就是让两个进程看到一份公共的资源,那么我们就把两个进程看到的同一份资源叫做临界资源
②把访问临街资源的代码叫做临界区,比如:读写部分操作的代码。
③在任意时刻,只允许一方访问临界区获取公共资源,这就是互斥。互斥也保证了访问的时候不会出错。
④Linux同时也会保证访问临界资源的原子性,所谓原子性就是完全相反的两种状态,比如:做了或者没做,没有正在做的状态。
⑤按照某种特定的顺序去访问临界资源的情况叫做同步
   Tip:绝大部分情况下,同步是互斥的前提条件
 同时,互斥也会引进另外的问题,就是饥饿问题:当某一段一直在访问临界资源,且当前端的访问优先级比较高,那么另
外一端 就无法被响应,此时就造成饥饿问题。
 相关的概念补充完成之后,我们来学习命名管道,刚才在匿名管道中,我们知道匿名管道的缺点就是只能让具有血缘关系
的进程进行通信,但是如果两个进程之间没有血缘关系,但是需要进行通信,此时就需要命名管道。
如何创建我们的命名管道: mkfifo();
 int mkfifo(const char* pathname,mode_t mode)
  返回值:成功返回0,失败返回-1
  参数:pathname 在指定文件路径下生成文件,这个文件叫做管道文件 mode:文件的权限
  头文件:<sys/types.h> <sys/stat.h>
 我们在linux中实现的时候,创建两个文件: client.c server.c 模拟服务器端和客户端,在server.c中创建管道文件,
client作为 写端,server作为读端。
server.c代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include <fcntl.h>


int main()
{
	umask(0);
	if(mkfifo("./mypipe",0666|S_IFIFO)<0)//当前文件路径下创建管道文件,权限为只读只写
	{
		perror("mkfifo");
		return 1;
	}
	int fd = open("./mypipe",O_RDONLY);//服务器以只读方式打开
	if(fd <0 )
	{
		perror("open");
		return 2;
	}
	char buff[1024];
	while(1)
	{
		size_t size = read(fd,buff,sizeof(buff)-1);
		if(size>0){
			printf("client say#%s\n",buff);
		}else{
			if(size==0){
				perror("read");
				break;
			}
		}
	}
	close(fd);
	return 0;
}
client.c代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include <fcntl.h>

int main()
{
	int fd = open("./mypipe",O_WRONLY);//客户端以写方式打开
	if(fd <0 )
	{
		perror("open");
		return 2;
	}
	char buff[1024];
	while(1)
	{
		printf("please enter#");
		fflush(stdout);
		size_t size = read(0,buff,sizeof(buff)-1);
		if(size>0){
			buff[size-1] = 0;
			write(fd,buff,sizeof(buff)-1);
		}else{
			if(size == 0){
				perror("read");
				break;
			}
		}
	}
	close(fd);
	return 0;
}
运行时,虽然client进行的写操作,但是命名管道是在server中创建的,所以在运行的时候,要先去运行
server,在linux中运行时,我们可以打开两个命令行窗口,一个运行client,一个运行server,在client中
输入的字符就会在server中显示出来。
 以上就是我们的命名管道。



Tip:限于编者水平,文章难免有不足之处,欢迎指正。
 如需转载,请注明出处!