管道通信

https://zhuanlan.zhihu.com/p/58489873

问题:

  1. 使用read读取匿名管道时,写端不关闭,读端读取完成后会阻塞等待写端继续写入,以便于继续接受写端的数据

管道本质上就是一个文件,前面的进程以写方式打开文件,后面的进程以读方式打开。这样前面写完后面读,于是就实现了通信。实际上管道的设计也是遵循UNIX的“一切皆文件”设计原则的,它本质上就是一个文件。Linux系统直接把管道实现成了一种文件系统,借助VFS给应用程序提供操作接口。

虽然实现形态上是文件,但是管道本身并不占用磁盘或者其他外部存储的空间。在Linux的实现上,它占用的是内存空间。所以,Linux上的管道就是一个操作方式为文件的内存缓冲区。

Linux上的管道分两种类型:

  1. 匿名管道 pipe
  2. 命名管道 fifo

这两种管道也叫做有名或无名管道。匿名管道最常见的形态就是我们在shell操作中最常用的”|”。它的特点是只能在父子进程中使用,父进程在产生子进程前必须打开一个管道文件,然后fork产生子进程,这样子进程通过拷贝父进程的进程地址空间获得同一个管道文件的描述符,以达到使用同一个管道通信的目的。此时除了父子进程外,没人知道这个管道文件的描述符,所以通过这个管道中的信息无法传递给其他进程。这保证了传输数据的安全性,当然也降低了管道了通用性,于是系统还提供了命名管道

使用linux函数实现命名管道

使用cat读取管道文件,会阻塞,直到可以从管道文件中读取到数据,也就是由另一个程序写入数据到管道文件

命名管道 FIFO(PHP只实现这一个) 内核实现,类似队列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
// 父子进程间通信
$file = 'fifo_x';
if(!posix_access($file,POSIX_F_OK)){
    if(posix_mkfifo($file, 0666)){
        fprintf(STDOUT, "fifo create ok\n");
    }
}
$pid = pcntl_fork();
if($pid == 0){
    $fd = fopen($file, "r");
    $data = fread($fd, 10);
    if($data){
        fprintf(STDOUT, "read process pid= %d ,rec: %s\n",posix_getpid(),$data);
    }
    exit(0);
}
$fd = fopen($file, "w");
$len = fwrite($fd, "hello",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 pid = %d\n",$pid);
}

非阻塞方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
// 父子进程间通信
$file = 'fifo_x';
if(!posix_access($file,POSIX_F_OK)){
    if(posix_mkfifo($file, 0666)){
        fprintf(STDOUT, "fifo create ok\n");
    }
}

$pid = pcntl_fork();
if($pid == 0){
    $fd = fopen($file, "r");
    stream_set_blocking($fd, 0); // 将文件设置为非阻塞方式
    $i=0;
    while(1){
        $data = fread($fd, 10); // read系统调用,不管有没有写数据,都直接返回
        echo $i++.PHP_EOL;

        if($data){
            fprintf(STDOUT, "read process pid= %d ,rec: %s\n",posix_getpid(),$data);
            break;
        }
    }

    exit(0);
}
$fd = fopen($file, "w");
stream_set_blocking($fd, 0); // 将文件设置为非阻塞方式
$len = fwrite($fd, "hello",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 pid = %d\n",$pid);
}

当读端关闭,写端还在继续写,此时无法写入,并会产生中断信号, SIGPIPE

当写端关闭,读端继续阻塞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
// 父子进程间通信
$file = 'fifo_x';
if(!posix_access($file,POSIX_F_OK)){
    if(posix_mkfifo($file, 0666)){
        fprintf(STDOUT, "fifo create ok\n");
    }
}
pcntl_signal(SIGPIPE, function ($signo) {
    fprintf(STDOUT, " sign number %s\n",$signo);
});
$pid = pcntl_fork();
if($pid == 0){
    $fd = fopen($file, "r");
    stream_set_blocking($fd, 0); // 将文件设置为非阻塞方式
    $i=0;
    while(1){
        $data = fread($fd, 10); // read系统调用,不管有没有写数据,都直接返回
        if($data){
            $i++;
            fprintf(STDOUT, "read process pid= %d ,rec: %s\n",posix_getpid(),$data);
            if($i==5){
                fclose($fd);break;
            }
            break;
        }
    }

    exit(0);
}
$fd = fopen($file, "w");
stream_set_blocking($fd, 0); // 将文件设置为非阻塞方式

while(1){
    pcntl_signal_dispatch();
    $len = fwrite($fd, "hello",5);

    fprintf(STDOUT, "write process pid = %d ,write len=%d\n",posix_getpid(),$len);
    
    $pid = pcntl_wait($status,WNOHANG);
    if($pid >0){
        fprintf(STDOUT, "exit pid = %d\n",$pid);
        break;
    }
}

fclose($fd);

unlink($file);