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)