【Linux】匿名管道 | 命名管道

为了实现进程间信息的交流,创造了管道。

想要让不同的进程看到同一份资源,前提是必须让不同进程看到同一份资源。

所以实现进程通信的方式是:在OS在创建一块内存,不属于A,也不属于B,同时A和B都是读取写入,那么就能实现进程间的通信。

进程间的通信分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

进程间的通信的管道分为匿名管道和命名管道,本文主要介绍匿名管道和命名管道。


什么是管道?

将一个进程连接到另外一个进程的流,叫做管道。

命名管道和匿名管道的差异在于进程是否具有血缘关系。

匿名管道是由子进程文件描述符继承的方式,命名管道是文件缓冲区的方式。

匿名管道

一个新文件被创建时,会由OS分配fd文件描述符,并开辟内存缓冲区(在内存中,由操作系统提供)

子进程的创建,OS先描述再组织,子进程会继承父进程的进程组织内容包括文件描述符

此时,不同的俩个PCB就会包含同一张文件描述符表,父进程和子进程都会指向同一份文件。

【Linux】匿名管道 | 命名管道_第1张图片

一个文件有读写,对于子进程和父进程都能和对方都是双向的。

但是管道只具有单向性,只能从一方写入,另一方读取数据,因此需要关闭不需要的fd。

在创建文件时,必须读写都打开,fork之后才能保证管道数据的交互。


创建匿名管道

pipe函数

int pipe(int pipefd[2]);

作用是创建无名管道

创建成功返回0,失败返回-1.

pipefd是一个2参数的输出形参数,指向文件描述符fd

数组元素

   含义
pipefd[0]      管道读端文件
pipefd[1]      管道写端文件

设计管道 子进程写入 父进程读取

1)建立管道

【Linux】匿名管道 | 命名管道_第2张图片

pipefd是输出形参数

管道会开启读端和写端

【Linux】匿名管道 | 命名管道_第3张图片

文件描述符的0 1 2 被默认开启

此时父进程的文件描述符3 和4 都会指向管道

2)创建子进程并写入数据

子进程继承父进程的fd文件描述符,同时读端和写端同时指向管道

为了单向通信,必须关闭子进程的读端。

利用snfprintf向缓冲区格式化写入数据。

【Linux】匿名管道 | 命名管道_第4张图片

子进程退出后,OS会自动关闭打开的读写端,因此直接exit。

3)父进程读数据

父进程需要关闭写端 pipefd[0]  读写读写 0是读 1是写

利用read函数读取数据的时候,最后一个位置不读,用户自定义放\0,视作字符串看待

【Linux】匿名管道 | 命名管道_第5张图片

4)进程等待,意外退出时,回收子进程状态

【Linux】匿名管道 | 命名管道_第6张图片

 点我提取完整代码

 在文件描述符角度理解匿名管道【Linux】匿名管道 | 命名管道_第7张图片

 管道的四个情况

  1. 正常情况,管道没有数据了,读端必须等待,直到有数据(写端输入)
  2. 正常情况,管道被写满了,写端必须等待,直到有数据位置(读端读取数据)
  3. 写端被关闭,读端会一直读取数据,直到读取到0,表示文件末尾
  4. 读端被关闭,写端一直写入,OS就会杀掉写端进程(向目标进程发送SIGPIEP(13)终止信号)

管道的五个特点

  1. 匿名管道允许带有血缘的进程之间相互通信,子孙,父子等等,常用于父子通信
  2. 匿名管道,默认给读写端提供同步机制
  3. 面向字节流
  4. 管道周期随进程
  5. 管道是单向通信的,半双工通信的一种

命名管道

命名管道是让俩个没有血缘关系的进程实现通信。本质还是要让进程看到同一份资源。

该管道是借助同一个文件实现的通信。

普通文件很难实现通信,即使通信也不安全。

命名管道和匿名管道一样,都是内存文件。命名管道在磁盘中有简单的映射,只不过这个映射的大小永远为0,并且不会被刷新到磁盘文件中。

mkfifo

mkfifo 命令创建文件

利用man mkfifo 指令查看mkfifo的资料

【Linux】匿名管道 | 命名管道_第8张图片

用于创建一个特殊的文件

mkfifo函数创建管道

man 3 mkfifo查看函数信息

【Linux】匿名管道 | 命名管道_第9张图片

函数原型

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

  1. 第一个参数为路径,如果不带路径,只有文件名,则在当前目录下创建
  2. 第二个参数为文件权限,受到权限掩码的影响
  3. 成功创建返回0 ,失败返回-1

命名管道的原理

【Linux】匿名管道 | 命名管道_第10张图片

本质在打开用一个文件,因为路径是唯一的。

如果进程A需要打开文件file,会在磁盘加载到内存上,文件内会分配缓冲区。file链接到fd文件符上,因此通过文件描述符就能找到struct并且找到缓冲区。如果进程B同时也访问file文件,磁盘则不会再次加载struct file 不会再分配缓冲区。因为OS不会做任何浪费空间和效率的行为,所以只需要一个计数器,负责链接时的增加,将进程B里PCB的fd链接到文件上。

这样进程A和进程B就能看到同一块缓冲区。


命名管道的举例

实现client和serve的通信

Makefile 自动化构建

.PHONY : all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11
client:client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f server client fifo

 为了一次make就能编译完成server和client  

通过依赖关系all  all会向下寻找server.cc 和client.cc编译

server客户端(读端)

创建fifo文件,如果创建失败,就打印错误码

【Linux】匿名管道 | 命名管道_第11张图片

main函数open以读的方式打开文件,文件不存在就会创建,利用goto语句回到文件的打开

【Linux】匿名管道 | 命名管道_第12张图片

从文件中读取数据

read函数将缓冲区的最后一个元素视作0 ,当作字符串

如果写端退出,读端会读到0 ,那么就要退出进程

【Linux】匿名管道 | 命名管道_第13张图片

#include
#include
#include
#include 
#include 
#include
#include
#include "comm.h"
//负责读取

bool Makefifo()
{
    int n = mkfifo(FILENAME,0666);
    if(n<0)
    {
        std::cerr<<"errno: "<0)
        {
            buff[n] = 0;
            std::cout<<"client say: "<

client客户端(写端)

首先需要打开文件

【Linux】匿名管道 | 命名管道_第14张图片

写入数据

每次读取一行,读取到数据后,就写入文件

写入失败就输出错误码

【Linux】匿名管道 | 命名管道_第15张图片

#include
#include
#include
#include
#include
#include
#include
#include"comm.h"
int main()
{
    int wfd =open(FILENAME,O_WRONLY);
    if(wfd<0)
    {
        std::cerr<<"error: "<

效果:

写端每次输入数据,读端立马就能获得数据,并打印。

总结

常见管道有匿名管道和命名管道,它们的区别是是否具有血缘关系。

管道的四个情况,正常情况下都会等待。如果写端关闭,那么读端会读到0,如果读端关闭,OS会杀死进程。

通信的本质就是让进程看到同一份资源。匿名管道借助拷贝到的文件描述符里指向同一个文件。

命名管道利用同一路径的fifo文件,通过同一块缓冲区就能看到同一份资源。

你可能感兴趣的:(linux,linux,服务器,管道)