java学习笔记

依旧是参考:

1
https://www.javasec.org/javase/JNI/

只不过版本有点老,会报一些错误,还是建议自己再修改代码

URLConnection

在java中,Java抽象出来了一个URLConnection类,它用来表示应用程序以及与URL建立通信连接的所有类的超类,通过URL类中的openConnection方法获取到URLConnection的类对象。

URLConnection:是所有与 URL 建立通信的类的父类。具体的协议支持类(如 HTTP、FTP 等)会继承 URLConnection。

使用URL发起一个简单的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.example;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

public class Main{
public static void main(String[] args)throws Exception{
URL url=new URL("http://www.baidu.com");
URLConnection connection=url.openConnection();
connection.setRequestProperty("user-agent","shallot");
connection.setConnectTimeout(1000);
connection.connect();
connection.getHeaderFields();
connection.getInputStream();
StringBuilder reponse=new StringBuilder();
BufferedReader in=new BufferedReader(
new InputStreamReader(connection.getInputStream())
);
String line;
while((line=in.readLine())!=null){
reponse.append("/n").append(line);
}
System.out.println(reponse.toString());
}
}

请求成功

JNI安全基础

JNI (Java Native Interface,JAVA 本地接口) 允许 Java 代码和其它编程语言编写的代码进行交互,主要为Java和Native层(C/C++)相互调用的接口规范,但是并不妨碍扩展其他语言。

感觉在学习的过程中已经遇到了很多次了看见了还是有点陌生,感觉自己是鱼的记忆哈哈(

JNI-定义native方法

首先在java中如果想要调用native方法需要现在类中定义一个native方法。

我们需要使用native关键字定义一个类似于接口的方法

JNI-生成类头文件

现在我们需要编译并生成c语言头文件

org_example_Test.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_example_Test */

#ifndef _Included_org_example_Test
#define _Included_org_example_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_example_Test
* Method: exec
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_Test_exec
(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

头文件命名强制性

javac生成的头文件名的函数命名方式具有非常强制性的约束方式的:比如Java_org_example_Test_exec中的Java_是固定的前缀缀,org_example_Test_exec是类名,exec是函数名。
(JNIEnv *, jclass, jstring)表示分别是JNI环境变量对象、java调用的类对象、参数入参类型。

JNI-基础数据类型

需要特别注意的是Java和JNI定义的类型是需要转换的,不能直接使用Java里的类型,也不能直接将JNI、C/C++的类型直接返回给Java

留个档,虽然用到的时候还是会查吧(

jstring转char*:env->GetStringUTFChars(str, &jsCopy)

char*转jstring: env->NewStringUTF(“Hello…”)

字符串资源释放: env->ReleaseStringUTFChars(javaString, p);

JNI-编写C/C++本地命令执行实现

写好了头文件之后,我们使用c语言来编写函数的最终实现方式。

Test.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "org_example_Test.h"
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

JNIEXPORT jstring JNICALL Java_org_example_Test_exec
(JNIEnv *env, jclass clazz, jstring command) {

if (command != NULL) {
// 将 Java 字符串转为 C 字符串
const char *cmd = (*env)->GetStringUTFChars(env, command, NULL);
if (cmd == NULL) {
return NULL;
}

// 执行命令
FILE *fp = popen(cmd, "r");
if (fp == NULL) {
(*env)->ReleaseStringUTFChars(env, command, cmd);
return NULL;
}

// 用于存储命令输出
char buffer[128];
char *result = NULL;
size_t result_len = 0;

// 持续读取命令输出并拼接成完整字符串
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
size_t chunk_len = strlen(buffer);
char *temp = realloc(result, result_len + chunk_len + 1);
if (temp == NULL) {
free(result);
pclose(fp);
(*env)->ReleaseStringUTFChars(env, command, cmd);
return NULL;
}
result = temp;
memcpy(result + result_len, buffer, chunk_len);
result_len += chunk_len;
result[result_len] = '\0'; // 保证字符串结束
}

// 清理资源
pclose(fp);
(*env)->ReleaseStringUTFChars(env, command, cmd);

// 转换为 Java 字符串返回
if (result != NULL) {
jstring jResult = (*env)->NewStringUTF(env, result);
free(result);
return jResult;
}
}

return NULL;
}

编译命令:

1
gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -shared -o libtest.jnilib org/example/Test.c

test1.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

import java.io.File;

public class Test1 {
public static void main(String[] args) {
String cmd = "whoami";
try {
File libPath = new File("libtest.jnilib");
System.load(libPath.getAbsolutePath());
Class<?> cls = Class.forName("org.example.Test");
String result = (String) cls.getMethod("exec", String.class).invoke(null, cmd);
System.out.println("命令执行结果:\n" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果:

做完了一系列操作来探究一下这些操作在干什么?

实际上使用 Java 编写一个 exec(String cmd) 方法,通过 JNI 技术调用 C 语言实现,执行系统命令,并把输出结果返回到 Java。

第一步:定义 Java 接口类(Test.java)
第二步:生成 JNI 头文件
第三步:用 C 实现命令执行逻辑(Test.c)
第四步:将 Test.c 编译为动态链接库(.so/.jnilib)
第五步:写 Java 测试类调用这个 native 方法(Test1.java)