需求
- 在c++程序中操作hdfs实现数据的读取和写入。
目标
- 写一个简单的c程序,将一句话写到hdfs上的一个文件中,并查看内容是否写进去了。
环境
- 虚拟机:centos6.7
- jdk :1.8
- gcc: 4.8.5
- hadoop:hadoop-2.7.7.tar.gz 、文档doc
思路
- 安装hadoop
- 在hdfs上创建一个文件:/liang/hello.txt
- 编写writepro.c程序,程序中调用hdfs API将“Hello, World!”写到上述文件中。
- 编译writepro.c成目标文件writepro
- 运行writepro
- 将hdfs上的/liang/hello.txt内容拷贝到本地,并使用cat命令查看内容。(完)
安装并运行
- 解压hadoop-2.7.6.tar.gz到/usr路径下
- 在etc/profile中配置HADOOP_HOME和 LD_LIBRARY_PATH
JAVA_HOME=/usr/java/jdk1.8.0_151
GCC_HOME=/usr/local/gcc-4.8.5
HADOOP_HOME=/usr/hadoop-2.7.6
PATH=$HADOOP_HOME/bin:$GCC_HOME/bin:$JAVA_HOME/bin:$PATH
export HADOOP_HOME GCC_HOME JAVA_HOME PATH
export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server
- 查看hadoop版本
[supdev@YZ-25-64-219]$ hadoop version
Hadoop 2.7.6
Subversion https://shv@git-wip-us.apache.org/repos/asf/hadoop.git -r 085099c66cf28be31604560c376fa282e69282b8
Compiled by kshvachk on 2018-04-18T01:33Z
Compiled with protoc 2.5.0
From source with checksum 71e2695531cb3360ab74598755d036
This command was run using /usr/local/hadoop-2.7.6/share/hadoop/common/hadoop-common-2.7.6.jar
在hdfs上建文件
伪分布式运行:Pseudo-Distributed Operation
-
运行成功在浏览器进入 http://ip:50070 页面。(要开启linux上的50070端口)
在hdfs上创建/liang/hello.txt文件
#创建liang目录
hdfs dfs -mkdir /liang
#在本地创建hello.txt文件
touch hello.txt
#将hello.txt发送到/liang目录下
hdfs dfs -put hello.txt /liang
-
验证文件已创建
编写writepro.c程序
官方c api文档:C API libhdfs
博客文档:c++ 操作HDFS、c++ 操作HDFS
编程程序writepro.c,并将文件上传到linux上。
//hdfs操作api头文件
#include "hdfs.h"
#include <stdio.h>
int main(int argc, char **argv) {
//连接hdfs
hdfsFS fs = hdfsConnect("default", 0);
//要写的文件路径
const char* writePath = "/liang/hello.txt";
//获取写文件对象
hdfsFile writeFile = hdfsOpenFile(fs, writePath, O_WRONLY |O_CREAT, 0, 0, 0);
if(!writeFile) {
fprintf(stderr, "Failed to open %s for writing!\n", writePath);
exit(-1);
}
//要写的内容
char* buffer = "Hello, World!";
//开始写内容
tSize num_written_bytes = hdfsWrite(fs, writeFile, (void*)buffer, strlen(buffer)+1);
//flush一下
if (hdfsFlush(fs, writeFile)) {
fprintf(stderr, "Failed to 'flush' %s\n", writePath);
exit(-1);
}
//关闭连接
hdfsCloseFile(fs, writeFile);
}
使用gcc编译成writepro
编译成最终执行文件的过程为:源码(.c)---编译-->目标文件(.o)---链接--->执行文件(后缀依据平台或编译器而定)。链接:将程序中使用的库函数与相关路径填入到目标文件中。
要编译成执行文件,就需要指定hdfs.h头文件的位置,以及使用的动态链接库libhdfs.so和动态链接库的位置。动态链接库使用JNI技术实现了c调用java。
hdfs.h头文件位置在$HADOOP_HOME/include 下,编译时指定使用libhdfs.so动态链接库,libhdfs.so动态链接库在$HADOOP_HOME/lib/native目录下。
程序运行过程中需要调用动态链接库,而动态链接库在磁盘中,而程序在内存中,速度不一致,所以要先将动态链接库先加载到高速缓存中。
-
参考:
动态链接库加载到缓存中
- 知识点:ldconfig与/etc/ld.so.conf
- ldconfig会将ld.so.conf中配置的目录下的动态链接库加载到高速缓存中
-
打开ld.so.conf,添加$HADOOP_HOME/lib/native目录
- 使上面配置其作用
# 下面执行完后不会有任何信息显示
ldconfig
- 可以使用下面命令查看所有被加载的动态链接库
ldconfig -p
显示格式:函数库名称 =》该函数库实际路径
编译成writepro文件
[root@CentOS usr]# gcc writepro.c -I$HADOOP_HOME/include -L$HADOOP_HOME/lib/native -lhdfs -o writepro
writepro.c: 在函数‘main’中:
writepro.c:14:11: 警告:隐式声明与内建函数‘exit’不兼容 [默认启用]
exit(-1);
^
writepro.c:19:71: 警告:隐式声明与内建函数‘strlen’不兼容 [默认启用]
tSize num_written_bytes = hdfsWrite(fs, writeFile, (void*)buffer, strlen(buffer)+1);
^
writepro.c:23:11: 警告:隐式声明与内建函数‘exit’不兼容 [默认启用]
exit(-1);
- -I指定"hdfs.h"头文件位置、-l指定使用的动态链接库、-L指定到哪里去寻找使用的动态链接库。
- -lhdfs是-l和hdfs两个部分组成,hdfs指明的是libhdfs.so这个函数库,其中lib和扩展名(.a或so)不用写。
- 编译会有警告。
运行writepro
参考文档:CLASSPATH
libhdfs.so动态链接库实现了c调用hdfs java程序,即其依赖于java,所以hadoop的jar包和相关配置文件也就需要加载到内存中。为此在运行前需要配置CLASSPAT环境变量,这样在程序运行过程中就可以根据CLASSPATH指定的路径去加载jar和相关配置到内存,以提供c通过JNI调用。
配置临时classpath
#hadoop classpath --glob命令会生成classpath所需内容
[root@CentOS /]# export CLASSPATH=`hadoop classpath --glob`
- 运行程序
[root@CentOS /]# ./usr/writepro
18/04/26 23:59:47 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
- 执行会有一个警告。
查看内容是否写入
- 将hdfs上hello.txt内容读取到helloresult.txt中
[root@CentOS /]# hadoop fs -get /liang/hello.txt helloresult.txt
[root@CentOS /]# cat helloresult.txt
Hello, World!
总结
-
不知道对不对
libjvm.so动态链接库找不到问题
1.编译时报 libjvm.so动态链接库找不到
[root@CentOS usr]# gcc writepro.c -I$HADOOP_HOME/include -L$HADOOP_HOME/lib/native -lhdfs -o writepro
/usr/bin/ld: warning: libjvm.so, needed by /usr/hadoop-2.7.6/lib/native/libhdfs.so, not found (try using -rpath or -rpath-link)
/usr/hadoop-2.7.6/lib/native/libhdfs.so: undefined reference to `JNI_CreateJavaVM@SUNWprivate_1.1'
/usr/hadoop-2.7.6/lib/native/libhdfs.so: undefined reference to `JNI_GetCreatedJavaVMs@SUNWprivate_1.1'
collect2: 错误:ld 返回 1
2.问题分析
libjvm.so位置:/usr/java/jdk1.8.0_151/jre/lib/amd64/server目录下
writepro依赖libhdfs.so,而libhdfs.so依赖libjvm.so,在编译时需要指定lib的路径,在执行时也需要指定寻找路径。
/usr/hadoop-2.7.6/lib/native/libhdfs.so依赖libjvm.so,动态链接库,而libjvm.so找不到。在编译时通过-Wl,rpath、LD_LIBRARY_PATH、ld.so.conf等配置libjvm.so路径。
解决方法
- 通过LD_LIBRARY_PATH环境变量指定libjvm寻找路径(上面编译就是采用这一种)
# 指定编译和执行寻找libjvm路径
export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server
gcc writepro.c -I$HADOOP_HOME/include -L$HADOOP_HOME/lib/native -lhdfs -o writepro
- 或者编译时通过-Wl,rpath指定寻找路径
#指定编译和执行寻找libjvm路径
gcc writepro.c -I$HADOOP_HOME/include -L$HADOOP_HOME/lib/native -lhdfs -Wl,-rpath=${JAVA_HOME}/jre/lib/amd64/server -o writepro
- 或者在ld.so.conf中配置运行时寻找路径
#-ljvm -L${JAVA_HOME}/jre/lib/amd64/server让其编译通过
gcc writepro.c -I$HADOOP_HOME/include -L$HADOOP_HOME/lib/native -lhdfs -ljvm -L${JAVA_HOME}/jre/lib/amd64/server -o writepro
#在ld.so.conf中添加libjvm路径让其其执行通过,配置完要执行ldconfig
[root@CentOS usr]# vim /etc/ld.so.conf
include ld.so.conf.d/*.conf
/usr/hadoop-2.7.6/lib/native
/usr/java/jdk1.8.0_151/jre/lib/amd64/server
查看writepro所依赖的动态函数库
- 上述三种方案编译后的writepro都可以正常执行,并且不冲突。下面查看writepro和libhdfs.so所依赖的动态链接库。
# 库=》对应路径(地址)
[root@CentOS usr]# ldd writepro
linux-vdso.so.1 => (0x00007ffe329d6000)
libhdfs.so.0.0.0 => /usr/hadoop-2.7.6/lib/native/libhdfs.so.0.0.0 (0x00007f32dbfdb000)
libc.so.6 => /lib64/libc.so.6 (0x000000310f800000)
libjvm.so => /usr/java/jdk1.8.0_151/jre/lib/amd64/server/libjvm.so (0x00007f32dafe3000)
libdl.so.2 => /lib64/libdl.so.2 (0x0000003110000000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x000000310fc00000)
/lib64/ld-linux-x86-64.so.2 (0x000000310f400000)
libm.so.6 => /lib64/libm.so.6 (0x0000003110800000)
root@CentOS usr]# ldd /usr/hadoop-2.7.6/lib/native/libhdfs.so
linux-vdso.so.1 => (0x00007fff8b31c000)
libjvm.so => /usr/java/jdk1.8.0_151/jre/lib/amd64/server/libjvm.so (0x00007fb42669f000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fb42649a000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb42627d000)
libc.so.6 => /lib64/libc.so.6 (0x00007fb425ee9000)
/lib64/ld-linux-x86-64.so.2 (0x000000310f400000)
libm.so.6 => /lib64/libm.so.6 (0x00007fb425c64000)
执行writepro出错
- writepro编译完成后,对于通过LD_LIBRARY_PATH和ld.so.conf方案解决libjvm问题的情况,如果将LD_LIBRARY_PATH和ld.so.conf对应的libjvm内容去掉,再次执行writepro时就会出现如下错误:
[root@CentOS usr]# ./writepro
./writepro: error while loading shared libraries: libjvm.so: cannot open shared object file: No such file or directory
- 查询ldd writepro也会出现not found问题
[root@CentOS usr]# ldd writepro
linux-vdso.so.1 => (0x00007ffdf21c6000)
libhdfs.so.0.0.0 => /usr/hadoop-2.7.6/lib/native/libhdfs.so.0.0.0 (0x00007f50526b9000)
libjvm.so => not found
libc.so.6 => /lib64/libc.so.6 (0x000000310f800000)
libjvm.so => not found
libdl.so.2 => /lib64/libdl.so.2 (0x0000003110000000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x000000310fc00000)
/lib64/ld-linux-x86-64.so.2 (0x000000310f400000)
解决办法:在LD_LIBRARY_PATH和ld.so.conf任何一个地方配置libjvm.so路径就可以,无需重新编译。
对于通过-Wl,rpath方式,如果libjvm.so路径变了,可以配置LD_LIBRARY_PATH和ld.so.conf来解决。或者重新编译指定新的路径。