进程间通信 --- 命名管道 有名管道存在与内存中,无名管道存在与文件系统中 换种角度看问题

1)open() 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO
      open() 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。
   简单一句话,只读等着只写,只写等着只读,只有两个都执行到,才会往下执行。

2)假如 FIFO 里没有数据,调用 read() 函数从 FIFO 里读数据时 read() 也会阻塞。这个特点和无名管道是一样的。

3)通信过程中,若写进程先退出了,就算命名管道里没有数据,

         调用 read() 函数从 FIFO 里读数据时也不阻塞
         若写进程又重新运行,则调用 read() 函数从 FIFO 里读数据时又恢复阻塞
5)通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到 SIGPIPE 信号)退出。
6)调用 write() 函数向 FIFO 里写数据,当缓冲区已满时 write() 也会阻塞

5)和 6)这两个特点和无名管道是一样的,这里不再验证   


命名管道可以以非阻塞标志(O_NONBLOCK)方式打开:
fd = open("my_fifo", O_WRONLY  |   O_NONBLOCK);  
fd = open("my_fifo", O_RDONLY   |   O_NONBLOCK);  
非阻塞标志(O_NONBLOCK)打开的命名管道有以下特点:
1、先以只读方式打开,如果没有进程已经为写而打开一个 FIFO, 只读 open() 成功,并且 open() 不阻塞。
2、先以只写方式打开,如果没有进程已经为读而打开一个 FIFO,只写 open() 将出错返回 -1。
3、read()、write() 读写命名管道中读数据时不阻塞。

1. 命名管道的概述

无名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了命名管道(FIFO),也叫有名管道、FIFO 文件。

命名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据

命名管道(FIFO)和无名管道(pipe)有一些特点是相同的,不一样的地方在于:

1、FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中。

2、当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。

3、FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。

2. 命名管道的创建

所需头文件:

#include <sys/types.h>

#include <sys/stat.h>

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

功能:

命名管道的创建。

参数:

pathname: 普通的路径名,也就是创建后 FIFO 的名字。

mode: 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同,相关说明请点此链接

返回值:

成功:0
失败:如果文件已经存在,则会出错且返回 -1。

示例代码如下:

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
  
int main(int argc, char *argv[])  
{  
    int ret;  
      
    ret = mkfifo("my_fifo", 0666); // 创建命名管道  
    if(ret != 0){   // 出错  
        perror("mkfifo");  
    }  
      
    return 0;  
}  

运行结果如下:

 

3. 命名管道的默认操作

后期的操作,把这个命名管道当做普通文件一样进行操作:open()、write()、read()、close()。但是,和无名管道一样,操作命名管道肯定要考虑默认情况下其阻塞特性

下面验证的是默认情况下的特点,即 open() 的时候没有指定非阻塞标志( O_NONBLOCK )。

1)open() 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO
      open() 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。

简单一句话,只读等着只写,只写等着只读,只有两个都执行到,才会往下执行。

只读端代码如下:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
int main(int argc, char *argv[])  
{  
		int fd;  
		int ret;  
		
		ret = mkfifo("my_fifo", 0666);  
		if(ret != 0)  
		{  
		perror("mkfifo");  
		}  
		
		printf("before open\n");  
		fd = open("my_fifo", O_RDONLY);//等着只写  
		if(fd < 0)  
		{  
		perror("open fifo");  
		}  
		
		printf("after open\n");  
		
		return 0;  
}  

只写端代码如下

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
int main(int argc, char *argv[])  
{  
		int fd;  
		int ret;     
		ret = mkfifo("my_fifo", 0666);  
		if(ret != 0)  
		{  
		perror("mkfifo");  
		}  
		
		printf("before open\n");  
		fd = open("my_fifo", O_WRONLY); //等着只读  
		if(fd < 0)  
		{  
		perror("open fifo");  
		}  
		
		printf("after open\n");  
		
		return 0;  
}

大家开启两个终端,分别编译以上代码,读端程序和写端程序各自运行,如下图,

大家自行验证其特点,因为光看结果图是没有效果,大家需要分析其运行过程是如何变化。

在各自运行之前会相互阻塞,相互等待

 

如果大家不想在 open() 的时候阻塞,我们可以以可读可写方式打开 FIFO 文件,这样 open() 函数就不会阻塞。

// 可读可写方式打开  
int fd = open("my_fifo", O_RDWR);

2)假如 FIFO 里没有数据,调用 read() 函数从 FIFO 里读数据时 read() 也会阻塞。这个特点和无名管道是一样的。

写端代码如下:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;  
      
    ret = mkfifo("my_fifo", 0666);//创建命名管道  
    if(ret != 0)  
    {  
        perror("mkfifo");  
    }  
      
    printf("before open\n");  
    fd = open("my_fifo", O_WRONLY); //等着只读  
    if(fd < 0)  
    {  
        perror("open fifo");  
    }  
    printf("after open\n");  
      
    printf("before write\n");  
    // 5s后才往命名管道写数据,没数据前,读端read()阻塞  
    sleep(5);  
    char send[100] = "Hello Mike";  
    write(fd, send, strlen(send));  
    printf("write to my_fifo buf=%s\n", send);  
      
    return 0;  
}  

读端代码如下:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;  
      
    ret = mkfifo("my_fifo", 0666); //创建命名管道  
    if(ret != 0)  
    {  
        perror("mkfifo");  
    }  
      
    printf("before open\n");  
    fd = open("my_fifo", O_RDONLY);//等着只写  
    if(fd < 0)  
    {  
        perror("open fifo");  
    }  
    printf("after open\n");  
      
    printf("before read\n");  
    char recv[100] = {0};  
      
    //读数据,命名管道没数据时会阻塞,有数据时就取出来  
    read(fd, recv, sizeof(recv));   
    printf("read from my_fifo buf=[%s]\n", recv);  
      
    return 0;  
}  

请根据下图自行编译运行验证:

 

3)通信过程中若写进程先退出了,就算命名管道里没有数据,调用 read() 函数从 FIFO 里读数据时不阻塞;

     若写进程又重新运行,则调用 read() 函数从 FIFO 里读数据时又恢复阻塞。

写端代码如下:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;  
      
    ret = mkfifo("my_fifo", 0666); // 创建命名管道  
    if(ret != 0)  
    {  
        perror("mkfifo");  
    }  
      
    fd = open("my_fifo", O_WRONLY); // 等着只读  
    if(fd < 0)  
    {  
        perror("open fifo");  
    }  
      
    char send[100] = "Hello Mike";  
    write(fd, send, strlen(send));  //写数据  
    printf("write to my_fifo buf=%s\n",send);  
      
    while(1); // 阻塞,保证读写进程保持着通信过程  
      
    return 0;  
}  

​​​​​​读端代码如下:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;  
      
    ret = mkfifo("my_fifo", 0666);// 创建命名管道  
    if(ret != 0)  
    {  
        perror("mkfifo");  
    }  
      
    fd = open("my_fifo", O_RDONLY);// 等着只写  
    if(fd < 0)  
    {  
        perror("open fifo");  
    }  
      
    while(1)  
    {  
        char recv[100] = {0};  
        read(fd, recv, sizeof(recv)); // 读数据  
        printf("read from my_fifo buf=[%s]\n",recv);  
        sleep(1);  
    }  
      
    return 0;  
}  

请根据下图自行编译运行验证:

5)通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到 SIGPIPE 信号)退出。
6)调用 write() 函数向 FIFO 里写数据,当缓冲区已满时 write() 也会阻塞。

5) 6)这两个特点和无名管道是一样的,这里不再验证,详情请看《无名管道》

4. 命名管道非阻塞标志操作

命名管道可以以非阻塞标志(O_NONBLOCK)方式打开:

  1. fd = open("my_fifo", O_WRONLY|O_NONBLOCK);  
  2. fd = open("my_fifo", O_RDONLY|O_NONBLOCK);  

非阻塞标志(O_NONBLOCK)打开的命名管道有以下特点:
1、先以只读方式打开,如果没有进程已经为写而打开一个 FIFO, 只读 open() 成功,并且 open() 不阻塞。

2、先以只写方式打开,如果没有进程已经为读而打开一个 FIFO,只写 open() 将出错返回 -1。
3、read()、write() 读写命名管道中读数据时不阻塞。

请根据以下代码自行编译运行验证。

写端代码如下:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;  
      
    ret = mkfifo("my_fifo", 0666); // 创建命名管道  
    if(ret != 0)  
    {  
        perror("mkfifo");  
    }  
      
    // 只写并指定非阻塞方式打开  
    fd = open("my_fifo", O_WRONLY|O_NONBLOCK);  
    if(fd<0)  
    {  
        perror("open fifo");  
    }  
      
    char send[100] = "Hello Mike";  
    write(fd, send, strlen(send));  
    printf("write to my_fifo buf=%s\n",send);  
    while(1);  
      
    return 0;  
}  

​​​​​​读端代码如下:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
  
int main(int argc, char *argv[])  
{  
    int fd;  
    int ret;  
      
    ret = mkfifo("my_fifo", 0666); // 创建命名管道  
    if(ret != 0)  
    {  
        perror("mkfifo");  
    }  
      
    // 只读并指定非阻塞方式打开  
    fd = open("my_fifo", O_RDONLY|O_NONBLOCK);  
    if(fd < 0)  
    {  
        perror("open fifo");  
    }  
      
    while(1)  
    {  
        char recv[100] = {0};  
  
        read(fd, recv, sizeof(recv));  
        printf("read from my_fifo buf=[%s]\n",recv);  
        sleep(1);  
    }  
      
    return 0;  
}  

换种角度看待这个管道问题:

1. 读还是写操作管道           2.管道内有无数据       3.读端先退出还是写端先退出   

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值