php超详细讲解命名管道

目录
  • 进程间为什么要通信
  • 进程如何实现通信
  • 常见进程通信方式
  • 管道概念
  • 命名管道实现
    • posix_mkfifo函数
    • 无血缘进程间通信

进程间为什么要通信

进程间通信的目的:

  • 数据传输:一个 进程需要将它的数据 发送给另一个进程。
  • 通知事件:一个进程需要向另一个或一组进程 发送消息,通知它(它们)发生了 某种事件(如进程终止时要通知父进程)。
  • 资源共享:多个进程之间 共享同样的资源 。为了做到这一点,需要内核提供互斥和同步机制。
  • 进程控制:有些进程 希望完全控制另一个进程的执行 (如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有状态信息

进程不是孤立的,一个足够大的项目绝对不是单一的进程可以支撑的起的。所以我们需要进程间通信,来满足不同进程间信息交互与传递的需求。

进程间通信本质上是进程与进程之间交换数据的手段。

进程如何实现通信

每个进程的用户地址空间都是独立的(进程通过虚拟内存地址达到进程相互隔离),一般而言是不能互相访问的,进程之间要通信必须通过内核,也就是说操作内核提供一个缓冲区,用户进程操作这个缓冲区【读写数据】来实现通信。

常见进程通信方式

今天我们要学习的是管道中的命名管道

管道概念

其实管道一共有两种,一种是匿名管道,它的特点是只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;比如父子进程,通常,一个匿名管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。

第二种是命名管道可以实现两个不相关进程之间进行数据交互,也能实现父子进程之间数据交互。命名管道是一种特殊类型的文件,有实体

对于命名管道,它可以在不相关的进程间也能相互通信。因为命名管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。

由于php扩展并没有提供匿名管道的封装,只提供了命名管道的,所有我们先不讲匿名管道,有兴趣的自行了解

命名管道实现

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,类似于队列

posix_mkfifo函数

php 通过 posix_mkfifo 函数创建命名管道文件

<?php
//定义管道文件
$file = 'fifo_dalei';
//posix_access() 函数检测管道文件是否存在,如果不存在使用posix_mkfifo() 函数创建一个命名管道文件
if(!posix_access($file, POSIX_F_OK)){
    if(posix_mkfifo($file,0666)){
        echo "命名管道创建成功\n";
    }
}

执行代码我们就得到了创建出来的管道文件 fifo_dalei ,我们查看文件权限位以 p 开头就表示这个文件是一个管道文件,当然我们也可以使用 file 命令查看fifo_dalei 文件类型是管道类型

我们可以直接在终端操作读写这个命名管道文件

比如:我们打开两个终端,A终端与B终端,A终端负责读命名管道中的数据,B终端负责往命名管道写入数据

我们通过 cat 命令 读取命名管道,如果命名管道没有数据 cat fifo_dalei 命令会阻塞住,直到有数据写入命名管道,cat fifo_dalei 才会输出数据内容并结束,反过来如果先将数据写入命名管道,却没有另一个进程读取命名管道的内容,写入命令依然会阻塞。

由此可以发现,管道这种通信方式效率低,不适合进程间频繁地交换数据。当然,它的好处,自然就是简单,同时也我们很容易得知管道里的数据已经被另一个进程读取了。

那么在php中我们如何使用命名管道文件呢,下面我们以父子进程通信为例

<?php
// 定义命名管道文件
$file = 'fifo_dalei';
// posix_access 函数检测当前文件是否存在
if(!posix_access($file, POSIX_F_OK)){
//posix_mkfifo 函数创建命名管道
    if(posix_mkfifo($file,0666)){
        echo "命名管道创建成功\n";
    }
}
// fork 创建子进程
$pid = pcntl_fork();
if($pid == 0){//子进程执行逻辑
	// 用读方式打开命名管道,需要注意的是如果命名管道内没有数据,fopen 函数会阻塞
    $fd = fopen($file, 'r');
    // fread 函数 读取命名管道,10个字节长度数据
    $data = fread($fd,10);
    if($data){
            fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data);
    }
    exit(0);
}
// 用写方式打开命名管道
$fd = fopen($file, 'w');
// 往命名管道写入数据 'dalei' 写入的长度为5
$len = fwrite($fd,'dalei',5);
fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len);
// 关闭管道文件
fclose($fd);
//回收执行完毕退出子进程,防止僵尸进程
$pid = pcntl_wait($status);
if($pid > 0){
    fprintf(STDOUT,"exit process pid = %d\n",$pid);
}

分析上面代码,首先父进程32023 写入数据dalei到命名管道,子进程 32024 读取命名管道中父进程写入的数据,然后 32024 子进程退出。

需要注意的是 fopen() 函数打开命名管道必须读端写端都打开,不然 fopen() 函数会阻塞

上面我们通过命令操作管道发现,管道内必须有内容,才能读取管道,不然会阻塞,php操作管道也同样有这个问题,看下面代码,我们通过 while 不停循环读取管道内容,但是写数据端写入一次会发生什么事情呢?

<?php
// 定义命名管道文件
$file = 'fifo_dalei';
// posix_access 函数检测当前文件是否存在
if(!posix_access($file, POSIX_F_OK)){
//posix_mkfifo 函数创建命名管道
    if(posix_mkfifo($file,0666)){
        echo "命名管道创建成功\n";
    }
}
// fork 创建子进程
$pid = pcntl_fork();
if($pid == 0){//子进程执行逻辑
	// 用读方式打开命名管道,需要注意的是如果命名管道内没有数据,fopen 函数会阻塞
    $fd = fopen($file, 'r');
    while(1){
    // fread 函数 读取命名管道,10个字节长度数据
    $data = fread($fd,10);
    if($data){
            fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data);
    }
    echo "hello\n";
     sleep(2);
    }
    exit(0);
}
// 用写方式打开命名管道
$fd = fopen($file, 'w');
// 往命名管道写入数据 'dalei' 写入的长度为5
$len = fwrite($fd,'dalei',5);
fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len);
// 关闭管道文件
#fclose($fd);
//回收执行完毕退出子进程,防止僵尸进程
$pid = pcntl_wait($status);
if($pid > 0){
    fprintf(STDOUT,"exit process pid = %d\n",$pid);
}

通过运行上面代码,当父进程只写入一次,子进程循环读,由于命名管道内无数据会造成 fread() 函数阻塞,无法往下执行也就无法打印出 hello,如何以非阻塞的方式读取数据呢?

下面我们就来介绍 stream_set_blocking() 函数实现非阻塞读命名管道,即命名管道无数据立即返回,并不会阻塞在fread() 函数

<?php
// 定义命名管道文件
$file = 'fifo_dalei';
// posix_access 函数检测当前文件是否存在
if(!posix_access($file, POSIX_F_OK)){
//posix_mkfifo 函数创建命名管道
    if(posix_mkfifo($file,0666)){
        echo "命名管道创建成果\n";
    }
}
// fork 创建子进程
$pid = pcntl_fork();
if($pid == 0){//子进程执行逻辑
	// 用读方式打开命名管道,需要注意的是如果命名管道内没有数据,fopen 函数会阻塞
    $fd = fopen($file, 'r');
    //设置非阻塞读取命名管道
    stream_set_blocking($fd,0);
    while(1){
    // fread 函数 读取命名管道,10个字节长度数据
    $data = fread($fd,10);
    if($data){
            fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data);
    }
    echo "hello\n";
    sleep(2);
    }
    exit(0);
}
// 用写方式打开命名管道
$fd = fopen($file, 'w');
// 往命名管道写入数据 'dalei' 写入的长度为5
$len = fwrite($fd,'dalei',5);
fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len);
// 关闭管道文件
#fclose($fd);
//回收执行完毕退出子进程,防止僵尸进程
$pid = pcntl_wait($status);

if($pid > 0){
    fprintf(STDOUT,"exit process pid = %d\n",$pid);
}

还有个需要注意的知识点,当写端写入数据的过程中,如果读端退出,写入数据将失败,并且产生中断信号 SIGPIPE, 下面是实验代码

<?php
// 定义命名管道文件
$file = 'fifo_dalei';
// posix_access 函数检测当前文件是否存在
if(!posix_access($file, POSIX_F_OK)){
//posix_mkfifo 函数创建命名管道
    if(posix_mkfifo($file,0666)){
        echo "命名管道创建成果\n";
    }
}
// 安装信号处理器,处理捕获信号
pcntl_signal(SIGPIPE,function($signo){
fprintf(STDOUT,"signo=%d\n",$signo);
});
// fork 创建子进程
$pid = pcntl_fork();
if($pid == 0){//子进程执行逻辑
	// 用读方式打开命名管道,需要注意的是如果命名管道内没有数据,fopen 函数会阻塞
    $fd = fopen($file, 'r');
    //设置非阻塞读取命名管道
    stream_set_blocking($fd,0);
    $i = 0;
    while(1){
    // fread 函数 读取命名管道,10个字节长度数据
    $data = fread($fd,10);
    if($data){
    	$i++;
    	if($i > 2){
    		fclose($fd);
    		break;
    	}
            fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data);
    }
    sleep(2);
    }
    exit(0);
}
// 用写方式打开命名管道
$fd = fopen($file, 'w');
stream_set_blocking($fd,0);
while(1){
// 信号分发(没有这个函数,信号无法被捕获)
pcntl_signal_dispatch();
// 往命名管道写入数据 'dalei' 写入的长度为5
$len = fwrite($fd,'dalei',5);
fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len);
sleep(2);
}
// 关闭管道文件
fclose($fd);
//回收执行完毕退出子进程,防止僵尸进程
$pid = pcntl_wait($status);
if($pid > 0){
    fprintf(STDOUT,"exit process pid = %d\n",$pid);
}

通过分析代码我们得知,我们定义了一个 $i 变量 用于累加,当累加数大于2,也就是read执行超过两次后读进程退出,写进程write将无法再向命名管道写入数据,并且产生信号数为13的中断信号

使用 kill -l 查看linux 中所有信号

无血缘进程间通信

写端

<?php
$file = 'fifo_dalei';
if(!posix_access($file,POSIX_F_OK)){
    if(!posix_mkfifo($file,0666)){
    }
}
$fd = fopen($file,'w');
while(1){
	//获取终端输入数据,大小限制1280字节
    $data = fgets(STDIN,1280);
    // 写入数据到命名管道,数据长度限制为10个字节
    $len = fwrite($fd,$data,10);
    fprintf(STDOUT,"pid=%d, write len = %d\n",posix_getpid(),$len);
}
fclose($fd);

读端 (一定要设置非阻塞读,不然写端,写入数据读端无法读取,只有写端进程退出才,读端才能全部读取出来)

<?php
$file = 'fifo_dalei';
if(!posix_access($file,POSIX_F_OK)){
    if(posix_mkfifo($file,0666)){

    }
}
$fd = fopen($file,'r');
//设置非阻塞读
stream_set_blocking($fd,0);
while(1){
//读取命名管道内容,读取长度限制为128字节
$data = fread($fd,128);
if($data){
    fprintf(STDOUT,"pid=%d,data=%s\n",posix_getpid(),$data);
}
}
fclose($fd);

通过上面的学习,我们已经了解了命名管道的用法与实现,当然大家还可以自己动手尝试编写命名管道的多种组合方式。比如说,多个读端,一个写端,读端是同时能读取到写入内容还是,一个能获取,一个获取不到,又或者多个写端一个读端会发生什么情况,这些大家都可以自己实现看看,毕竟实践出真知,不动手只听别人说可不行哦!

到此这篇关于php超详细讲解命名管道的文章就介绍到这了,更多相关php命名管道内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • PHP命名空间(namespace)原理与用法详解

    本文实例讲述了PHP命名空间(namespace)原理与用法.分享给大家供大家参考,具体如下: PHP 命名空间(namespace)是在PHP 5.3中加入的,它可以解决以下两类问题: 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突. 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性. 我们在默认情况下,所有常量.类和函数名都放在全局空间下,就和PHP支持命名空间之前一样,命名空间通过关键字namespace

  • PHP 命名空间和自动加载原理与用法实例分析

    本文实例讲述了PHP 命名空间和自动加载原理与用法.分享给大家供大家参考,具体如下: PHP 命名空间 php5.3 之后引入了命名空间的特性,从本质上讲,命名空间就是一个容器,你可以将类.函数和变量放在其中,在命名空间中,你可以无条件地访问这些项,在命名空间之外,必须导入或引用命名空间,才能访问它所包含的项. 声明命名空间 namespace my; require_one 'outputter3.php'; class outputter { // 输出数据 public function

  • php 命名空间(namespace)原理与用法实例小结

    本文实例讲述了php 命名空间(namespace)原理与用法.分享给大家供大家参考,具体如下: 命名空间一个最明确的目的就是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误.这种情况下只要避免命名重复就可以解决,最常见的一种做法是约定一个前缀,也可以采用命名空间的方式解决 TestSpace.php <?php namespace Demo\Test; //声明一个命名空间Demo class Test1 { static function test() {

  • 详解PHP中的命名空间

    命名空间其实早在PHP5.3就已经出现了.不过大部分同学可能在各种框架的使用中才会接触到命名空间的内容,当然,现代化的开发也都离不开这些能够快速产出的框架.这次我们不从框架的角度,仅从简单的代码角度来解析一下命名空间的概念和使用. 首先,我们要定义命名空间是个什么东西. 其实就像操作系统的目录一样,命名空间就是为了解决类似于操作系统中同一个文件夹不能有相同的文件名一样的问题.假设我们只有一个文件,一个目录,那么在这个目录中,是不能有两个完全相同的文件的.如果有这样名称完全相同的文件,那么操作系统

  • PHP命名空间用法实例分析

    本文实例讲述了PHP命名空间用法.分享给大家供大家参考,具体如下: 在讲解命名空间之前,我们先了解一个问题. 我们在网站根目录创建一个文件夹,在文件夹中创建a.php <?php class Apple{ function get_into(){ echo "this is A"; } } 然后再创建一个b.php <?php class Apple{ function get_into(){ echo "this is B"; } } 再创建一个ind

  • 详细分析PHP 命名空间(namespace)

    PHP 命名空间(namespace)是在PHP 5.3中加入的,如果你学过C#和Java,那命名空间就不算什么新事物. 不过在PHP当中还是有着相当重要的意义. PHP 命名空间可以解决以下两类问题: 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突. 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性. 定义命名空间 默认情况下,所有常量.类和函数名都放在全局空间下,就和PHP支持命名空间之前一样. 命名空间通

  • PHP 命名空间原理与用法详解

    本文实例讲述了PHP 命名空间原理与用法.分享给大家供大家参考,具体如下: 命名空间适用于 (PHP 5 >= 5.3.0, PHP 7) 使用命名空间基础 PHP 命名空间类似于文件系统, 在文件系统中访问一个文件有三种方式: 相对文件名形式如foo.txt.它会被解析为 currentdirectory/foo.txt,其中 currentdirectory 表示当前目录.因此如果当前目录是 /home/foo,则该文件名被解析为/home/foo/foo.txt. 相对路径名形式如subd

  • 如何理解PHP核心特性命名空间

    提出 PHP 在 5.3 后提出了命名空间用来解决组件之间的命名冲突问题,主要参考了文件系统的设计: 同一个目录下不允许有相同的文件名 - 同一个命名空间下不允许有相同的类: 不同的目录可以有同名文件 - 不同的命名空间可以有相同的类: 定义 使用namespace关键字来定义一个命名空间.其中,顶层命名空间通常为厂商名,不同开发者的厂商命名空间是唯一的.命名空间不需要与文件目录一一对应,但是最好遵守PSR-4规范. <?php namespace Symfony\Component\HttpF

  • php超详细讲解命名管道

    目录 进程间为什么要通信 进程如何实现通信 常见进程通信方式 管道概念 命名管道实现 posix_mkfifo函数 无血缘进程间通信 进程间为什么要通信 进程间通信的目的: 数据传输:一个 进程需要将它的数据 发送给另一个进程. 通知事件:一个进程需要向另一个或一组进程 发送消息,通知它(它们)发生了 某种事件(如进程终止时要通知父进程). 资源共享:多个进程之间 共享同样的资源 .为了做到这一点,需要内核提供互斥和同步机制. 进程控制:有些进程 希望完全控制另一个进程的执行 (如 Debug

  • 超详细讲解python正则表达式

    目录 正则表达式 1.1 正则表达式字符串 1.1.1 元字符 1.1.2 字符转义 1.1.3 开始与结束字符 1.2 字符类 1.2.1 定义字符类 1.2.2 字符串取反 1.2.3 区间 1.2.4 预定义字符类 1.3 量词 1.3.1 量词的使用 1.3.2 贪婪量词和懒惰量词 1.4 分组 1.4.1 分组的使用 1.4.2 分组命名 1.4.3 反向引用分组 1.4.4 非捕获分组 1.5 re模块 1.5.1 search()和match()函数 1.5.2 findall()

  • C语言 超详细讲解链接器

    目录 1 什么是链接器 2 声明与定义 3 命名冲突 3.1 命名冲突 3.2 static修饰符 4 形参.实参.返回值 5 检查外部类型 6 头文件 1 什么是链接器 典型的链接器把由编译器或汇编器生成的若干个目标模块,整合成一个被称为载入模块或可执行文件的实体–该实体能够被操作系统直接执行. 链接器通常把目标模块看成是由一组外部对象组成的.每个外部对象代表着机器内存中的某个部分,并通过一个外部名称来识别.因此,==程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部

  • Java 超详细讲解抽象类与接口的使用

    目录 一.抽象类 1.抽象类的语法 2.抽象类的特性 3.抽象类的作用 二.接口 1.接口的概念 2.接口使用 3.接口特性 4.实现多个接口 5.接口间的继承 6.常用的接口 (1)Comparable接口 (2)Cloneable接口 三.Object类 一.抽象类 在Java中,如果一个类被abstract修饰称为抽象类,抽象类中被abstract修饰的方法称为抽象方法,抽象方法不用给出方法体. 1.抽象类的语法 //抽象类:被abstract修饰的类 public abstract cl

  • Redis超详细讲解高可用主从复制基础与哨兵模式方案

    目录 高可用基础---主从复制 主从复制的原理 主从复制配置 示例 1.创建Redis实例 2.连接数据库并设置主从复制 高可用方案---哨兵模式sentinel 哨兵模式简介 哨兵工作原理 哨兵故障修复原理 sentinel.conf配置讲解 哨兵模式的优点 哨兵模式的缺点 高可用基础---主从复制 Redis的复制功能是支持将多个数据库之间进行数据同步,主数据库可以进行读写操作.当主数据库数据发生改变时会自动同步到从数据库,从数据库一般是只读的,会接收注数据库同步过来的数据. 一个主数据库可

  • Java 超详细讲解字符流

    目录 一.字符流的由来 二.编码表 字符集: Unicode字符集: UTF-8编码规则: 三.字符串中的编码解码问题 编码方法(IDEA): 解码方法(IDEA): 四.字符流的编码解码问题 四.字符流写数据的五种方法 五.字符流读数据的两种方法 一.字符流的由来 由于使用字节流操控中文时不是很方便,Java就提供了字符流来进行操控中文 实现原理:字节流+编码表 为什么用字节流进行复制带有中文的文本文件时没有问题? 因为底层操作会自动进行字节拼接成中文 怎样识别该字节是中文呢? 汉字在存储时,

  • C语言 struct结构体超详细讲解

    目录 一.本章重点 二.创建结构体 三.typedef与结构体的渊源 四.匿名结构体 五.结构体大小 六.结构体指针 七.其他 一.本章重点 创建结构体 typedef与结构体的渊源 匿名结构体 结构体大小 结构体指针 其他 二.创建结构体 先来个简单的结构体创建 这就是一个比较标准的结构体 struct people { int age; int id; char address[10]; char sex[5]; };//不要少了分号. 需要注意的是不要少了分号. 那么这样创建结构体呢? s

  • C语言超详细讲解顺序表的各种操作

    目录 顺序表是什么 顺序表的结构体 顺序表的接口函数 顺序表相关操作的菜单 顺序表的初始化 添加元素 陈列元素 往最后加元素 往前面加元素 任意位置加元素 删除最后元素 删除前面元素 删除任意元素 整体代码(fun.h部分) 整体代码(fun.cpp部分) 整体代码(主函数部分) 结果展示 顺序表是什么 顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素.使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数

  • Java超详细讲解三大特性之一的封装

    目录 封装 封装的概念 Java中的包 java中类的成员-构造器 java中的this关键字 总结 说到面向对象则不得不提面向对象的三大特征:封装,继承,多态.那么今天就和大家先来介绍什么是封装. 封装 封装的概念 将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来对隐藏的信息进行操作和访问. 为什么需要封装? 当我们创建一个类的对象后,我们可以通过“对象.属性”的方式,对对象的属性进行赋值.这里赋值操作要受到 属性的数据类型和存储范围的制约.除此之外,没有其他制约

  • C语言超详细讲解结构体与联合体的使用

    目录 结构体 offsetof-宏 位段 枚举 联合体(共用体) 结构体 结构体内存对齐问题: 当我们在计算结构体的大小时,我们便需要清楚的知道结构体内存对齐是什么. 存在内存对齐的原因可细分为两个: 平台原因: 不是所有的硬件平台都能方位任意地址上的任意数据:某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常. 性能原因: 首先内存对齐可以提高程序的性能,当访问未对其的内存空间时,有时候处理器需要进行两次访问,而当访问对齐的内存时,只需要一次就够了.这同时也被叫做 用空间换取

随机推荐