Linux详解 --- 进程间通信1 (匿名管道与命名管道)


在这里插入图片描述
受排版的限制,上面的思维导图显示的不是很完整。下面是思维导图源文件的获取方式:
思维导图获取
提取码: diid


问题:进程间通信是什么?
 进程间通信就是 进程和进程 之间进行通信。
问题:进程间通信的目的?(为什么要有进程间通信)
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(如进程终止时会去通知父进程)
进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦住另一个进程的所有陷入和异常,并能够及时知道它的状态改变

问题:进程间通信的分类?
 管道:匿名管道pipe、命名管道
 System V IPC:System V 消息队列、System V 共享内存、System V 信号量
 POSIX IPC:消息队列、共享内存、信号量、互斥量、条件变量、读写锁
问题:进程间通信的本质是什么?
 进程间通信的本质就是让不同的进程看到同一份资源

问题:怎么做到进程间通信?
在这里插入图片描述

ps: 由于进程运行时具有独立性,因此进程间通信时,一般要借助第三方(OS)资源 #OS要提供一段内存区域,该区域能被双方进程看到
 通信的本质就是“数据的拷贝”

匿名管道pipe

匿名管道的使用

   int pipe(int fd[2]);

参数:
 fd:文件描述符数组,其中fd[0]代表read读端;fd[1]代表write写端;
返回值:
 成功返回0;失败返回错误代码

演示

//父进程通过管道给子进程发消息
#include <iostream>
#include <unistd.h>    
#include <sys/wait.h>    
#include <sys/types.h>    
#include <cstring>    
using namespace std;    
    
int main()    
{    
  int fd[2];    
  pipe(fd);    //创建匿名管道
  pid_t id = fork();    
    
  if(id == 0)    
  {
    //子进程
    close(fd[1]); //关闭写端    
    char buffer[128];
    ssize_t num = read(fd[0], buffer, sizeof(buffer));    
    buffer[num] = '\0';    //从文件中读取的字符串并不是以'\0'为结尾的
    cout << buffer;
  }    
  else    
  {    
    //父进程    
    close(fd[0]); //关闭读端    
    const char* buffer = "this is father porcess!\n";    
    write(fd[1], buffer, strlen(buffer));    
    wait(NULL);    
  }    
    
  return 0;    
} 

匿名管道的原理

在这里插入图片描述
 父进程先创建一个匿名管道,然后再创建子进程。在子进程创建的时候,子进程会拷贝父进程的task_struct和files_struct结构体,而他们的files_struct结构体中的文件描述符指向了同一个文件,这样子进程就能看到父进程所创建的匿名管道了。
Tip:
 管道虽然使用的是文件的方案,其实OS并不会把数据刷新到磁盘当中 (匿名和命名都是),因为刷新到磁盘的话,会降低效率(有IO参与,效率肯定会下降)。//刷新到磁盘也没有意义,2个进程只是借助文件实现通信,所以并不需要刷新到磁盘当中

父子进程通过匿名管道通信的过程

在这里插入图片描述
 首先,父进程先使用pipe函数创建一个管道,一个进程在创建管道的时候,默认是同时打开读端fd[0]和写端fd[1]的


在这里插入图片描述
 父进程调用fork函数,子进程在被创建时会拷贝父进程的task_struct和files_struct结构体,而在files_struct中有2个文件描述符已经指向了一个匿名管道,所以子进程也能看到这个管道。


在这里插入图片描述
 为了实现父子间通过管道进行通信,必须让其中1个关闭读端,另一个关闭写端。

管道的特点

  • 管道只能进行单向通信
  • 管道是半双工通信 (数据在同一时刻只能向1个方向流动,想要进行双向通信需要建立2个管道)
  • 管道的内部提供了互斥与同步的机制
  • 管道的生命周期跟随进程,一般情况下,进程退出,管道就会被释放
  • 匿名管道适合具有血缘关系的进程进行通信 (父子进程、兄弟进程)
  • 管道提供流式服务

这里大概解释一下管道特点中的个别几点:
问题:什么是半双工通信?什么是全双工通信?
 全双工:在同一时刻,发送数据 和 接收数据 能同时进行 #同一时刻能双向
 半双工:在同一时刻,只能进行发送 或 接收      #同一时刻只能单向

问题:什么是同步与互斥机制?
 这个概念我将会放在多线程博客当中去讲解,这里就大概的介绍一下。互斥就是同一时刻只能有一个进程/线程正在使用临界资源,同步就是在确保了临界资源安全的前提下,以特定的顺序让进程/线程去访问临界资源。

管道的读写规则

  1. 正常进行写和读时,当write写完,关闭该进程的写端fd[1]。此时,读端read函数的返回值为0
  2. 写端还没写完,读端就关闭了,此时写端进程会被OS杀掉 (收到SIGPIPE信号)
  3. 不write,一直read,由于写端接收不到数据,就会出于阻塞状态
  4. 不read,一直write,当管道满了之后,写端就会进入阻塞状态

总结:
读管道:
 1. 管道中有数据,read返回实际读到的字节数
 2. 管道中无数据:
  (1) 管道写端全部被关闭,read返回0
  (2) 写端没有全部被关闭,在read处阻塞等待
写管道:
 1. 管道读端全部被关闭,进程异常终止 (OS发送SIGPIPE信号给写端)
 2. 读端没有全部被关闭:
  (1) 管道未满时,write继续写入数据
  (2) 管道写满了,write阻塞

管道的大小

在这里插入图片描述

命名管道

 由于匿名管道只能作用于具有共同祖先的进程间通信,然而大部分的进程实际上都是不相关的,所以我们引入了命名管道,命名管道可以让完全不相干的2个进程进行通信。命名管道虽然是打开一个目录下的文件,但是实际上也是在内存当中进行操作,并不会刷新到磁盘当中。

命名管道的使用

命令行中创建:

	mkfifo filename

程序中创建:

	int mkfifo(const char* filename, mode_t mode);	
	//包含于<sys/types.h>  <sys/stat.h>

参数:
 filename:创建出来的命名管道的名称
 mode:命名管道权限的设置

演示

//实现client端给server端发消息
//server.cpp
#include <iostream>
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    
using namespace std;    
#define FILE_NAME "myfifo"    
      
int main()    
{    
	mkfifo(FILE_NAME, 0666);//mkfifo的第一参数是文件名,它会给你创建个以这个为名字的命名管道    
	int fd = open(FILE_NAME, O_RDONLY);    
	char buffer[128];    
	while(1)    
    {
	ssize_t num = read(fd, buffer, sizeof(buffer));
	buffer[s] = 0; 
     //下面的代码是server端接收client端的字符串的消息
     if(num < 0)
     {
       cout << "read error!" << endl;
       break;
     }
     else if(num == 0)
     {         
       cout << "End of File!" << endl;    
       break;
     }                                   
     else                              
     {    
       cout << buffer << endl;            
     }                                                    
   }                                        
   return 0;              
}   


//client.cpp
#include <iostream>
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    
using namespace std;    
#define FILE_NAME "myfifo"    
    
int main()    
{    
  int fd = open(FILE_NAME, O_WRONLY);    
  const char* msg = "hello server, this is client";      
  int cnt = 5;
  while(cnt--)    
  {    
    write(fd, msg, strlen(msg));    
    sleep(1);    
  }
  sleep(100);
  return 0;
} 

匿名管道 VS 命名管道

匿名管道与命名管道的唯一区别就在于 ----- 管道的打开方式不同
 匿名管道由pipe函数创建,并在创建时打开
 命名管道由mkfifo函数创建,打开需要使用open

管道的应用

  1. 可以实现进程之间互相传递消息(字符串)
  2. 可以让一个进程帮助另一个进程执行指令 (另一个进程接收消息后,使用execl函数执行指令)
  3. 可以让另一个进程帮助进行计算
  4. 可以实现“文件的拷贝”   //文件的上传、下载,实际上都是文件的拷贝

  • 47
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 52
    评论
评论 52
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值