Linux下进程通信 命名管道FIFO

Linux下进程通信 匿名管道pipe

3 命名管道(FIFO/named PIPE)

  • 匿名管道(pipe):这个方式的一个缺陷,就是这些就进程都是由一个共同的祖先进程启动,这给我们在不相关的进程之间交换数据带来了不方便。
    无名管道只能用于具有亲缘关系的进程之间,这就大大限制了管道的使用。而有名管道可以解决这个问题,他可以实现任意两个进程之间的通信。

有名管道的创建可以使用mkfifo函数,函数的使用类似于open函数的使用,可以指定管道的路径和打开的模式。

3.1 什么是命名管道

  • 命名管道也被称为FIFO或者named pipe,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的匿名管道类似。
  • FIFO(First in, First out) 为一种特殊的文件类型,它在文件系统中有对应的路径。
  • 当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。
  • 叫FIFO,因为管道本质上是一个先进先出的队列数据结构 ,最早放入的数据被最先读出来,从而保证信息交流的顺序
  • 写模式的进程向FIFO中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。FIFO的好处在于我们可以通过 文件的路径来识别管道,从而让没有亲缘关系的进程之间建立连接 **

3.2 命名管道的读写规则

  • 1、从FIFO中读取数据的约定:如果一个进程为了从FIFO中读取数据而阻塞打开了FIFO,那么该进程内的读操作 为设置了阻塞标志的读操作。
  • 2、从FIFO中写入数据的约定:如果一个进程为了想FIFO中写入数据而阻塞打开了FIFO,那么该进程内的写操作 为设置了阻塞标志的写操作。

3.3 命名管道的安全问题

  • 让写操作原子化,怎么才能使写操作原子化呢?
  • 系统规定,在一个以O_WRONLY(即阻塞方式)打开的FIFO中,如果写入的数据长度小于等于PIPE_BUF,那么或者写入全部字节,或者一个字节都不写入。如果所有的写请求都是发往一个阻塞的FIFO的,并且每个写请求的数据长度小于等于PIPE_BUF字节,系统就可以确保数据绝不会交错在一起。

相比TCP通信方式

命名管道常常用于应用程序之间的通迅,由于不需要进行序列化和反序列化操作,效率是非常高的。相比TCP通信方式,效率更高,但比共享内存要低点。

Android JNI之命名管道(FIFO/mkfifo)的使用

场景描述:在JNI层使用mkfifo,并且读写数据,同时在Android Java层读写fifo数据。

Android Java层读写fifo数据,获取fifo路径后,new File能够成功,说明路径是对的,但是然后用各种Stream,都无法成功获得可用的流,进而无法继续操作。
暂时的结论,Java层读写fifo数据比较有困难,new 各种Stream时会阻塞住出不来,有空了再试,可能的原因可以看完本文,可能与底层申请只读权限不会成功有关系。

先说说在JNI层使用mkfifo,首先,FIFO的路径怎么确定,不是随便给个路径就能mkfifo成功的,经过试验,路径可以这样确定,在ANDROID JAVA层写如下代码:

String fifoName="my_test_fifo";     
  String seprator = File.separator;
  //get full fifo auto
  String dataDir=context.getApplicationInfo().dataDir;
 
  String fullFifoPath=dataDir+seprator+fifoName;//this is the full path you want!!!

获取到完整的FIFO路径(fullFifoPath)后,便可以通过JNI接口传入底层C/C++代码,底层利用这个完整的路径进行mkfifo,open,write,read等操作:


//fifo操作自己本身需要的几个头文件,其它需要的头文件自己看着加
#include 
#include 
#include 
 
int fd_fifo;//fifo handle
char *fifoPath="your fifo path";//不要hard code,换成你自己的fifo路径
if (mkfifo(fifoPath, 0666)<0 && errno != EEXIST)
{
    //LOGI("can't create fifo ");
   return; 
}
 
//mkfifo success
 
fd_fifo=open(fifoPath, O_RDONLY | O_NONBLOCK);
if( fd_fifo<0)
{
   // LOGI("fd=%d,can't open fifoPath%s",fd_fifo,fifoPath);
   return;
}
else
{
   // LOGI("fd=%d,Success open fifoPath=%s",fd_fifo,fifoPath);
}

需要额外提醒大家一点的就是,在进行open操作的时候,关于权限问题,这是在ANDROID上调用底层代码特有的一个诡异的地方,还没找到具体的原因,大家注意一下就行了:

如果打开时权限参数为只读,会失败,如下代码:

fd_fifo=open(fifoPath, O_WRONLY | O_NONBLOCK);

返回值fd_fifo会是-1,代表失败,如果把O_NONBLOCK去掉,则会一直阻塞住出不来。但是如果打开权限是可读写(O_RDWR)和只读(O_RDONLY)都会成功,同时,一个诡异的地方,一旦申请过O_RDWR,后面再用O_WRONLY就会成功了,下面是我依次以不同权限open的返回值结果:

2020-12-10 19:00:30.005 5327-5383/com.example.myapplication I/log_c: testfd_wo=-1 //O_WRONLY | O_NONBLOCK,失败
2020-12-10 19:00:30.005 5327-5383/com.example.myapplication I/log_c: testfd_wr=94//O_RDWR | O_NONBLOCK,成功
2020-12-10 19:00:30.005 5327-5383/com.example.myapplication I/log_c: testfd_ro=95//O_RDONLY | O_NONBLOCK,成功
2020-12-10 19:00:30.005 5327-5383/com.example.myapplication I/log_c: testfd_w=96//O_WRONLY | O_NONBLOCK,成功,因为前面以O_RDWR的权限open过,所以这里成功了!

JAVA层new各种stream会阻塞住出不来的现象,暂时能得到的原因是:java里面new stream的时候默认是以阻塞方式去new 的,虽然路径存在,但是申请不到,就一直阻塞住。

参考

命名管道文件的使用
使用命名管道实现进程间通信
Android JNI之命名管道(FIFO/mkfifo)的使用(2)

你可能感兴趣的:(Linux下进程通信 命名管道FIFO)