设为首页 收藏本站
查看: 946|回复: 0

[经验分享] hadoop下JNI调用c++

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-7-13 08:47:40 | 显示全部楼层 |阅读模式
  声明:本文主要参考http://www.iyunv.com/Linux/2012-12/75535.htm,如有侵权,请通知删除。
  按照上面的参考实现了一遍,碰到了一些问题,还有一些该注意的地方,以下详细阐述。
  尝试分两个阶段进行:
阶段一:在linux跑通一个单机版的JNI程序,即用java调用c++。
阶段二:将上面的程序放到hadoop上跑通。


阶段一:
  1.创建一个工程文件夹jni1,jni1下创建bin和src文件夹。
  [hadoop@Master jni1]$ mkdir bin
[hadoop@Master jni1]$ mkdir src
  在src里创建FakeSegmentForJni.java



package FakeSegmentForJni ;  
public class FakeSegmentForJni {  
public static native String SegmentALine (String line);  
static  
{  
System.loadLibrary("FakeSegmentForJni");  
}  
}

  2.回到上层目录,用javac命令编译FakeSegmentForJni类,生成.class文件
  [hadoop@Master jni1]$ javac -d ./bin ./src/*.java
  你会发现bin目录下多了一个FakeSegmentForJni的文件夹,文件夹下有个FakeSegmentForJni.class的文件
  3. 在FakeSegmentForJni.class的基础上,用javah命令生成c++函数的头文件。此时在bin目录下操作。
  [hadoop@Master bin]$ javah -jni -classpath . FakeSegmentForJni.FakeSegmentForJni
  其中classpath表示.class文件所在目录,“.”表示当前目录(注:这个点和前后都有空格的!);后面的参数,第一个“FakeSegmentForJni”表示package名称,第二个“FakeSegmentForJni”表示class名称。敲完命令后,就能在当前目录下发现c++函数的头文件FakeSegmentForJni_FakeSegmentForJni.h。打开看一下,主要将java类中的static函数转成了c++接口,内容如下:



/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class FakeSegmentForJni_FakeSegmentForJni */
#ifndef _Included_FakeSegmentForJni_FakeSegmentForJni
#define _Included_FakeSegmentForJni_FakeSegmentForJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     FakeSegmentForJni_FakeSegmentForJni
* Method:    SegmentALine
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_FakeSegmentForJni_FakeSegmentForJni_SegmentALine
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif

  第一行说不要修改这个文件。
  4. 生成了一个c++头文件,再生成cpp文件。其实用c++的理由是尽量利用现有的、成熟的代码,所以这一步,一般不是功能性开发,而是写个wrapper包装现有的代码——如果是纯功能性开发,那直接用java的了。
  生成cpp文件,还是在bin目录下,cpp文件如下:(注:cpp中的函数声明一定要和头文件里一样!)



#include   
#include   
#include   
#include "FakeSegmentForJni_FakeSegmentForJni.h"  
/* * Class:    FakeSegmentForJni_FakeSegmentForJni
* * Method:    SegmentALine
* * Signature: (Ljava/lang/String;)Ljava/lang/String;
* */  
JNIEXPORT jstring JNICALL Java_FakeSegmentForJni_FakeSegmentForJni_SegmentALine  
(JNIEnv *env, jclass obj, jstring line)  
{  
char buf[128];  
const char *str = NULL;  
str = env->GetStringUTFChars(line, false);  
if (str == NULL)  
return NULL;  
strcpy (buf, str);  
strcat (buf, "--copy that\n");  
env->ReleaseStringUTFChars(line, str);  
return env->NewStringUTF(buf);  
}

   [hadoop@Master bin]$ ll
total 12
drwxrwxr-x 2 hadoop hadoop 4096 Nov 28 10:48 FakeSegmentForJni
-rw-rw-r-- 1 hadoop hadoop  700 Nov 28 11:01 FakeSegmentForJni_FakeSegmentForJni.cpp
-rw-rw-r-- 1 hadoop hadoop  562 Nov 28 10:52 FakeSegmentForJni_FakeSegmentForJni.h
  5. 在本地环境下编译出c++动态库。为啥要强调“在本地环境”?字面上的意思就是,你在windows下用JNI,就到windows下编译FakeSegmentForJni_FakeSegmentForJni.cpp文件,生成dll;在linux下,就到linux下(32位还是64位自己搞清楚)编译FakeSegmentForJni_FakeSegmentForJni.cpp文件,生成.so文件。为啥非要这样?这就涉及到动态库的加载过程,每个系统都不一样。
  [hadoop@Master bin]$ g++ -I/usr/java/jdk1.6.0_31/include -I/usr/java/jdk1.6.0_31/include/linux FakeSegmentForJni_FakeSegmentForJni.cpp -fPIC -shared -o libFakeSegmentForJni.so
  命令有点长,不过意思很容易。“-I”表示要包含的头文件。正常来讲,系统路径都已经为g++设置好了。不过jni.h是java的头文件,不是c++的,g++找不到,只好在编译的时候告诉编译器。
  我这里jni.h是在/usr/java/jdk1.6.0_31/include目录下,还要包含/usr/java/jdk1.6.0_31/include/linux,因为jni.h文件也调用也这里面的东西,不然会有错误。
  还有库文件的文件名前必须要有lib,不然即使指定了正确的库文件路径也会找不到
  这样,就有了libFakeSegmentForJni.so文件
  [hadoop@Master bin]$ ll
total 20
drwxrwxr-x 2 hadoop hadoop 4096 Nov 28 10:48 FakeSegmentForJni
-rw-rw-r-- 1 hadoop hadoop  700 Nov 28 11:01 FakeSegmentForJni_FakeSegmentForJni.cpp
-rw-rw-r-- 1 hadoop hadoop  562 Nov 28 10:52 FakeSegmentForJni_FakeSegmentForJni.h
-rwxrwxr-x 1 hadoop hadoop 7703 Nov 28 11:03 libFakeSegmentForJni.so
  6.本地java程序调用FakeSegmentForJni.so。回到src目录下,创建TestFakeSegmentForJni.java



package FakeSegmentForJni;  
import java.io.IOException;  
import java.net.URI;  
import java.net.URL;  
/**
* This class is for verifying the jni technology.  
* It call the function defined in FakeSegmentForJni.java
*  
*/  
public class TestFakeSegmentForJni {  
public static void main(String[] args) throws Exception {  
System.out.println ("In this project, we test jni!\n");  
String s = FakeSegmentForJni.SegmentALine("now we test FakeSegmentForJni");  
System.out.print(s);  
}
}

  回到jni1目录下,执行[hadoop@Master jni1]$ javac -d ./bin ./src/*.java,jni1/bin/FakeSegmentForJni目录下会多出一个TestFakeSegmentForJni.class文件。
  7.打成jar包。
  回到bin目录下,执行[hadoop@Master bin]$ jar -cvf TestFakeSegmentForJni.jar FakeSegmentForJni,jar命令把当前FakeSegmentForJni文件夹下的所有class文件都打成了一个jar包。所以,bin目录下会多出个.jar文件。
  8.运行。
  [hadoop@Master bin]$ java -Djava.library.path='.' -cp TestFakeSegmentForJni.jar FakeSegmentForJni.TestFakeSegmentForJni
  这里不知道为什么,用-jar来运行的话,会出现“no main manifest attribute, in TestFakeSegmentForJni.jar”的错误,改成-cp就好了。
  运行结果如下:
  [hadoop@Master bin]$ java -Djava.library.path='.' -cp TestFakeSegmentForJni.jar FakeSegmentForJni.TestFakeSegmentForJni   
In this project, we test jni!
now we test FakeSegmentForJni--copy that

阶段二:
  1.在eclipse上创建mapreduce工程。前提:hadoop安装好并成功运行,配置好eclipse开发环境,需要装插件开发mapreduce工程,具体参考http://www.iyunv.com/hequn/articles/3438301.html 中的四。
  新建FakeSegmentForJni.java和JniTest.java类,新建类的时候包的地址和包名要和前面生成库的时候一样,不然貌似即使库加载正确,在调用库中函数的时候也会出现error。我也不知道为什么,反正这样就不错了,就是有点不方便,以后再研究。
  具体的路径,见下图:
DSC0000.jpg
  FakeSegmentForJni.java:



package FakeSegmentForJni;
public class FakeSegmentForJni {
public static native String SegmentALine (String line);
static
{
System.loadLibrary("FakeSegmentForJni");
}
}

  JniTest.java:



package FakeSegmentForJni;

import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Mapper.Context;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class JniTest {
public static class MapTestJni extends Mapper {  
protected String s;  
protected void setup(Context context) throws IOException, InterruptedException  
{  
s = FakeSegmentForJni.SegmentALine("jni-value");  
}  
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {  
context.write(value, new Text(s.toString()));  
}  
}

public static class ReduceTestJni extends Reducer {  
protected void reduce(Text key, Iterable values, Context context)  
throws IOException, InterruptedException {  
String outString = "";  
for (Text value: values)  
{  
outString = value.toString();  
context.write(key, new Text(outString));
}  

}  
}
public void runTestJni (String[] args) throws Exception {  
//  the configuration  
Configuration conf = new Configuration();  
//   conf.set("mapred.job.tracker", "192.168.178.92:9001");
//   conf.addResource(new Path("E:\\HadoopWorkPlat\\hadoop-1.1.2\\conf\\hdfs-site.xml"));
//  String[] ars = { "/user/hadoop/0", "/user/hadoop/colorToGrayOutput2" };
GenericOptionsParser goparser = new GenericOptionsParser(conf, args);  
String otherargs [] = goparser.getRemainingArgs();
removeDir(new Path(args[2]), conf);
// the job  
Job job;  
job = new Job(conf, "@here-TestFakeSegmentForJni-hadoopJni");  
job.setJarByClass(JniTest.class);  
// the mapper  
job.setMapperClass(MapTestJni.class);  
job.setMapOutputKeyClass(Text.class);  
job.setMapOutputValueClass(Text.class);  
// the reducer  
job.setReducerClass(ReduceTestJni.class);  
job.setOutputKeyClass(Text.class);  
job.setOutputValueClass(Text.class);  
job.setNumReduceTasks(1);  
// the path  
FileInputFormat.addInputPath(job, new Path(otherargs[1]));  
FileOutputFormat.setOutputPath(job, new Path(otherargs[2]));  
job.waitForCompletion(true);  
}

public static void main(String[] args) throws Exception {  
System.out.println ("In this project, we test jni!\n");  
// test jni on linux local  
/*String s = FakeSegmentForJni.SegmentALine("now we test FakeSegmentForJni");
System.out.print(s);*/  
// test jni on hadoop  
new JniTest().runTestJni(args);  
} // main
@SuppressWarnings("deprecation")
public static void removeDir(Path outputPath, Configuration conf)
throws IOException {
FileSystem fs = FileSystem.get(conf);
if (fs.exists(outputPath)) {
fs.delete(outputPath);
}
}
}

  其中runTestJni(),GenericOptionsParser那两行是关键,用来分析和执行hadoop命令中传进来的特殊参数。配合命令行中的命令(下文写),他把动态库分发到tasknode上,路径与jar的执行路径相同。
通常用jar包的形式运行hadoop程序,所需的参数,如:输入路径、输出路径、mapper、combiner、reducer等都可以用Job来设置,不需要额外的参数。命令行中少了这些参数,会显得短很多。尤其hadoop命令行一般都挺长,就很方便。相反地,采用c++ streaming的方式来运行程序的时候,就需要用-input、-output等参数来指定相关参数。不过,在jar包中,除了用Job外,也可以用GenericOptionsParser来解析上述命令行中的参数,只要命令行配合有相应的输入,GenericOptionsParser就可以解析。对于-input、-output等来讲,没有必要这样做。还有一个参数是-files,就是把-files后面的文件(多个文件用逗号间隔)同jar包一起分发到tasknode中,这个参数,刚好可以将我们的动态库分发下去。
“goparser.getRemainingArgs();”这条语句,是在GenericOptionsParser解析完特殊参数之后,获得剩下的参数列表,对于我们来讲,剩下的参数就是main函数所在的类名、输入路径和输出路径,参见下面的命令行。
  2.把工程打包成jar包。Export->Runnable JAR file->launch configuration(选择相应的工程)->Export destination->Extract required libraries into generated JAR.
  3.把生成的jar包传到Master上。这里的jar是JniTest3.jar。
  [hadoop@Master ~]$ ll -a | grep JniTest3.jar
-rw-r--r--   1 hadoop hadoop 27495223 Nov 28 13:14 JniTest3.jar
  4.执行。
  [hadoop@Master ~]$ hadoop jar JniTest3.jar -files /home/hadoop/Documents/java/jni1/bin/libFakeSegmentForJni.so FakeSegmentForJni.TestFakeSegmentForJni input output
  hadoop jar 命令后面跟随的第一个参数一定是打好的jar包,在本例中是TestFakeSegmentForJniHadoop.jar文件及其路径
由于在控制函数中用了GenericOptionsParser,jar包后面就必须紧跟需要设定的参数,这里,我们的参数是“-files /xxx/TestJni/libFakeSegmentForJni.so”,表示把本地路径“/xxx/TestJni/”中的libFakeSegmentForJni.so文件随jar包分发下去。
剩下的就比较容易了,分别是main函数所在的类名、输入路径、输出路径
  5.结果。
  output中的part-r-00000中的内容是key    jni-value--copy that
  至此,调用成功。
  
  FAQ:
  1.返回int[]型数据。主要代码如下:




JNIEXPORT jintArray JNICALL Java_ArrayTest_initIntArray(JNIEnv *env, jclass cls, int size)
{
jintArray result;
result = (*env)->NewIntArray(env, size);
if (result == NULL) {
return NULL; /* out of memory error thrown */
}
int i;
// fill a temp structure to use to populate the java int array
jint fill[256];
for (i = 0; i < size; i++) {
fill = 0; // put whatever logic you want to populate the values here.
}
// move from the temp structure to the java structure
(*env)->SetIntArrayRegion(env, result, 0, size, fill);
return result;
}

  貌似没有jint fill[256];返回都是不成功的,有点像析构的感觉。env->NewIntArray()new出来的都会没了。先放到jint fill[256]中,然后放到jintArray中。
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-85991-1-1.html 上篇帖子: Hadoop学习笔记—20.网站日志分析项目案例(一)项目介绍 下篇帖子: hadoop Shell命令详解
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表