一、什么是管道
如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。
举个例子,在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。
二、pipe调用
pipe函数的原型如下:
#include <unistd.h>
int pipe(int file_descriptor[2]);
我们可以看到pipe函数的定义非常特别,该函数在数组中墙上两个新的文件描述符后返回0,如果返回返回-1,并设置errno来说明失败原因。
数组中的两个文件描述符以一种特殊的方式连接起来,数据基于先进先出的原则,写到file_descriptor[1]的所有数据都可以从file_descriptor[0]读回来。由于数据基于先进先出的原则,所以读取的数据和写入的数据是一致的。
pipe是基于文件描述符工作的,所以在使用pipe后,数据必须要用底层的read和write调用来读取和发送。
不要用file_descriptor[0]写数据,也不要用file_descriptor[1]读数据,其行为未定义的,但在有些系统上可能会返回-1表示调用失败。数据只能从file_descriptor[0]中读取,数据也只能写入到file_descriptor[1],不能倒过来。
例子:
首先,我们在原先的进程中创建一个管道,发送消息,然后在进程中的线程接收消息(android开启进程是?),并关闭管道
jni下
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<jni.h>
#include<signal.h>
#include<android/log.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native-activity", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "native-activity", __VA_ARGS__))
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
static char CLASS_NAME[] = "com/sailor/Pipe";
static int fd[2];
static int nret = -1;
void DestroyUnNamePipe(JNIEnv *env, jobject thiz)
{
if(nret == 0)
{
close(fd[0]);
close(fd[1]);
}
}
jint CreateUnNamePipe(JNIEnv *env, jobject thiz)
{
int ret = -1;
ret = pipe(fd);
nret = ret;
if(ret != 0)
{
LOGE("create unname pipe error:%d" ,ret);
}
return ret;
}
jstring ReadUNPMessage(JNIEnv *env, jobject thiz)
{
char buffer[80+1];
int ret = -1;
if(nret == 0)
{
ret = read(fd[0], buffer, sizeof(buffer));
LOGE("read ret:%d" , ret);
if(ret == -1)
{
return NULL;
}
}
return (*env)->NewStringUTF(env, buffer);
}
jint SendUNPMessage(JNIEnv *env, jobject thiz, jstring message)
{
int ret = -1;
if(nret == 0)
{
const char *pMsg = (*env)->GetStringUTFChars(env, message, 0);
ret = write(fd[1], pMsg, strlen(pMsg));
LOGE("send ret: %d", ret);
(*env)->ReleaseStringUTFChars(env, message, pMsg);
}
return ret;
}
static JNINativeMethod mehods[] = {
{ "createUnNamePipe", "()I",
(void *) CreateUnNamePipe },
{ "sendUNPMessage", "(Ljava/lang/String;)I", (void *) SendUNPMessage },
{ "readUNPMessage", "()Ljava/lang/String;", (void *) ReadUNPMessage },
{ "destroyUnNamePipe", "()V", (void *) DestroyUnNamePipe }
};
static int registerNativeMethods(JNIEnv *env, const char* className,
const JNINativeMethod* methods, int numMethods)
{
int rc;
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'\n", className);
return -1;
}
if (rc = ((*env)->RegisterNatives(env, clazz, methods, numMethods)) < 0) {
LOGE("RegisterNatives failed for '%s' %d\n", className, rc);
return -1;
}
return 0;
}
static int register_jni(JNIEnv *env)
{
return registerNativeMethods(env, CLASS_NAME, mehods, NELEM(mehods));
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env = NULL;
jint result = -1;
//获取JNI版本
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK)
{
LOGE("GetEnv failed!");
return result;
}
if (register_jni(env) < 0)
{
LOGE("register method failed!");
return result;
}
return JNI_VERSION_1_4;
}
java下调用:
package com.sailor;
public class Pipe {
static {
// 加载动态库
System.loadLibrary("JNIPipe");
}
public native int createUnNamePipe();
public native int sendUNPMessage(String msg);
public native String readUNPMessage();
public native void destroyUnNamePipe();
}
public void startPipe(View view){
pipe = new Pipe();
pipe.createUnNamePipe();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
Log.d("jacklam", "wait read msg");
Log.d("jacklam", "" + pipe.readUNPMessage());
pipe.destroyUnNamePipe();
break;
}
}
});
thread.start();
pipe.sendUNPMessage("just test");
}
当写数据的管道没有关闭,而又没有数据可读时,read调用通常会阻塞,但是当写数据的管道关闭时,read调用将会返回0而不是阻塞。注意,这与读取一个无效的文件描述符不同,read一个无效的文件描述符返回-1。
另因android sdcard为fat32,而mkfifo只是调用用mknod()来创建一个特殊的文件,和fat32不支持,所以不能创建命名管道
int mkfifo(const char *filename, mode_t mode);