JDBC实践
- 前言
- 一、JNI编程基本流程
- 1.定义native本地方法
- 2.编译生成class文件
- 3.根据class文件生成h文件
- 4.根据h文件编写函数的具体实现
- 5.编译本地方法源文件并生成共享库链接
- 6.编写测试java类
- 二、需要用到的JNI方法
- 1. 数组
- 2.修改或读取对象的属性值
- 三、通过JNI创建本地消息队列
- 1.消息队列的基本原理
- 2.消息队列的基本使用
- 3.native方法定义
- 4.根据h文件编写函数的具体实现
- 5.编写测试java类与C进程demo
- 6.编译并运行
- 四、通过JNI创建本地共享内存
- 1.共享内存的基本原理
- 2.共享内存的基本使用
- 3.native方法定义
- 4.根据h文件编写函数的具体实现
- 5.编写测试java类
- 6.编译并运行
- 总结
前言
本地的C语言写的进程需要与java写的进程交互,使用共享内存和消息队列的方式比较高效。而java由于JVM的内存管理,需要native方法在linux内存创建共享内存和消息队列与操作系统上其他进程通信。
环境:centos7、jni、java
一、JNI编程基本流程
1.定义native本地方法
以一个简单的hello打印为例。
/home/xd/Documents/java_workspace/jni/jni_lib为存放该类需要使用的本地方法动态链接库的目录。
public class JNIDemo {
public native int sayHello();
}
2.编译生成class文件
本地方法的头文件需要依靠class文件生成。
3.根据class文件生成h文件
可以自己手动生成,也可以在IDEA添加生成方法。我比较懒,采用IDEA生成的方法。
采用IDEA设置里的External Tools创建自定义工具,使用环境的javah程序根据编译生成的class文件产生头文件,指定存放到out的同级目录lib_jni。
4.根据h文件编写函数的具体实现
生成的h文件里没有定义形参。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNIDemo */
#ifndef _Included_JNIDemo
#define _Included_JNIDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNIDemo
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT int JNICALL Java_JNIDemo_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
编写C文件
#include<jni.h>
#include <stdio.h>
#include "JNIDemo.h"
JNIEXPORT int JNICALL Java_JNIDemo_sayHello
(JNIEnv *env, jobject thisObj)
{
printf("Hello World!\n");
return 1;
}
5.编译本地方法源文件并生成共享库链接
linux平台是生成.so文件,语句比较固定,包含jdk的头文件路径。因为为比较懒,写在sh脚本里。
#!/bin/bash
gcc -I/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.282.b08-1.el7_9.x86_64/include/ -I/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.282.b08-1.el7_9.x86_64/include/linux/ -fPIC -shared -o $1.so $1.c
6.编写测试java类
需要先load共享库链接,然后就可以像使用普通方法一样使用本地方法了。so文件可以随意移动到任意位置,load全路径即可。
public class JNIDemo {
static{
System.load("/home/xd/Documents/java_workspace/jni/jni_lib/JNIDemo.so");
}
public native int sayHello();
public static void main(String[] args) {
int i = 0;
System.out.println(new JNIDemo().sayHello());
}
}
二、需要用到的JNI方法
1. 数组
将基本类型数组某一区域复制到缓冲区中的一组函数。
void Get{PrimitiveType}ArrayRegion (JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf)
使用说明:Get{PrimitiveType}ArrayRegion 替换为下表的某个实际基本类型元素访问器例程名,ArrayType 替换为对应的数组类型,NativeType 替换为该例程对应的本地类型。
参数:
env:JNI 接口指针。
array:Java数组名称。
start:起始下标。
len:要复制的元素数。
buf:目的缓冲区。
例子:
( *env ) ->GetByteArrayRegion ( env, msg, 0 , mslen, msgq. msg_text ) ;
从java字节数组msg的0下标开始复制mslen个字节到C字符数组msg_text中。
将缓冲区中某一区域复制到基本类型数组的一组函数。
void Set{PrimitiveType}ArrayRegion (JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
使用说明: 将 Set{PrimitiveType}ArrayRegion 替换为表中的实际基本类型元素访问器例程名。将 ArrayType 替换为对应的数组类型。将 NativeType 替换为该例程对应的本地类型。
参数:
env:JNI 接口指针。
array: Java 数组。
start:起始下标。
len:要复制的元素数。
buf:源缓冲区。
例子:
( *env ) ->SetByteArrayRegion ( env, msg, 0 , readmslen, msgq. msg_text ) ;
从C字符数组msg_text复制readmslen个字节到java的msg字节数组中。要求readmslen不可以大于java字节数组大小。
2.修改或读取对象的属性值
可以用来将C的结构体传给java类对象。
1.根据obj对象获取class类
jclass clazz = env->GetObjectClass(obj);
2.根据类,成员属性名称,成员属性类型获取域ID
jfieldID GetFieldID(jclass clazz, const char name, const char sig)
参数:
clazz:指定对象的类
name:这个域(Field)在 Java 类中定义的名字
sig:这个域(Field)的类型描述符
3.获取实例域的变量值
NativeType Get{type}Field(jobject obj, jfieldID fieldID)
参数:
obj:某个 Java 对象实例
fieldID:这个变量的域ID
4.修改实例域的变量值
void Set{type}Field(jobject obj, jfieldID fieldID, NativeType value)
参数:
obj:需要修改的 Java 对象实例
fieldID:这个变量的域ID
value:需要设置的值
C语言例子:
jclass clazz = (*env)->GetObjectClass(env, obj);
jfieldID id_field = (*env)->GetFieldID(env, clazz, "id", "I");
jint left = (*env)->GetIntField(env, obj, id_field);
(*env)->SetIntField(env, obj, id_field, 1);
三、通过JNI创建本地消息队列
1.消息队列的基本原理
进程通过约定好的key值创建好在内核创建消息队列,对应key返回该消息队列的qid。进程向队形写数据类似于向链表尾部添加数据,取数据相当于从链表头取出数据。
2.消息队列的基本使用
定义消息结构
struct message
{
long msg_type; //消息标识符
char msg_text [ MSG_MAX ] ; // 消息
} ;
创建
msqid = msgget ( key, IPC_CREAT | 0666 )
发送
ret = msgsnd ( msqid, &msgq, mlen, 0 )
获取
readmslen = msgrcv ( msqid, &msgq, MSG_MAX, mstype, 0 )
3.native方法定义
创建和收发
public class MsgQ
{
public native static int msgget ( int msg_key ) ;
public native static int msgsnd ( int msqid, int type, byte [ ] msg, int len ) ;
public native static int msgrcv ( int msqid, byte [ ] msg, int len, int type ) ;
}
4.根据h文件编写函数的具体实现
5.编写测试java类与C进程demo
java测试类。由于消息队列是阻塞接收,因此有必要使用多线程实现收发。
import java.util.Scanner;
/**
* System V 消息队列JNI
* @author diaoyf
*
*/
public class MsgQ
{
static{
System.load("/home/xd/Documents/java_workspace/jni/jni_lib/MsgQ.so");
}
public native static int msgget ( int msg_key ) ;
public native static int msgsnd ( int msqid, int type, byte [ ] msg, int len ) ;
public native static int msgrcv ( int msqid, byte [ ] msg, int len, int type ) ;
public static void main ( String [ ] args )
{
new Thread(()->{
//获得消息id
int msqid = MsgQ. msgget ( 0xFF ) ;
System.out.println(msqid);
while(true){
//消息
Scanner s = new Scanner(System.in);
String ms = s.nextLine();
System.out.println("send:"+ms);
//转换为字节数组
byte [ ] msBytes = ms. getBytes ( ) ;
//发送消息,发送类型定义为9527
int ret = MsgQ. msgsnd ( msqid, 9527 , msBytes, msBytes. length ) ;
}
}).start();
new Thread(()->{
int msqid = MsgQ. msgget ( 0xFF ) ;
System.out.println(msqid);
while(true){
byte [ ] buf = new byte [ 1024 ] ;
//接收消息,接收类型定义为9528
int len = MsgQ. msgrcv ( msqid, buf, 1024 , 9528 ) ;
String msr = new String ( buf, 0 , len ) ;
System . out . println ( "接收到的消息:" + msr ) ;
}
}).start();
}
}
C测试程序
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#define MSG_MAX 8192
#define mslen 1024
struct message
{
long msg_type; //消息标识符
char msg_text [ MSG_MAX ] ; // 消息
} ;
void* snd_pthread(void* arg);
void* recv_pthread(void* arg);
int main()
{
int msqid;
int key = 0xff;
if ( ( msqid = msgget ( key, IPC_CREAT | 0666 ) ) == -1 )
{
perror ( "[JNI ERROR]msgget Error" ) ;
}
printf("main:%d\n",msqid);
pthread_t pid1 = -1;pthread_t pid2 = -1;
if(pthread_create(&pid1,NULL,recv_pthread,(void *)(long)msqid)!=0)
{
printf("fail to create a pthread");
return -1;
}
//snd_pthread((void *)(long)msqid);
if(pthread_create(&pid2,NULL,snd_pthread,(void *)(long)msqid)!=0)
{
printf("fail to create a pthread");
return -1;
}
printf("%d %d \n",pid1,pid2);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
return 0;
}
void* recv_pthread(void* arg)
{
/* 消息结构 */
int mstype = 9527;
int readmslen;
int msqid = (int)(long)arg;
printf("recv msqid:%d\n",msqid);
while(1)
{
struct message msgq;
/* 复制消息类型 */
msgq. msg_type = mstype;
memset(msgq.msg_text,0,sizeof(msgq.msg_text));
/* 从消息队列读出消息,到msgq */
if ( ( readmslen = msgrcv ( msqid, &msgq, MSG_MAX, mstype, 0 ) ) < 0 )
{
perror ( "[JNI ERROR]msgrcv Error" ) ;
}
if ( mslen < readmslen )
{
perror ( "[JNI ERROR]msgrcv Error: jbyteArray msg too small." ) ;
}
printf("recv:%s\n",msgq.msg_text);
}
}
void* snd_pthread(void* arg)
{
/* 消息结构 */
int mstype = 9528;
int ret;
int msqid = (int)(long)arg;
printf("snd msqid:%d\n",msqid);
while(1)
{
struct message msgq;
memset(msgq.msg_text,0,sizeof(msgq.msg_text));
scanf("%s",(msgq.msg_text));
/* 消息结构 */
printf("enter:%s\n",msgq.msg_text);
int mlen = strlen(msgq.msg_text);
/* 复制消息类型 */
msgq. msg_type = mstype;
/* 调用系统函数msgsnd */
if ( ( ret = msgsnd ( msqid, &msgq, mlen, 0 ) ) < 0 )
{
perror ( "[JNI ERROR]msgsnd Error" ) ;
}
}
}
6.编译并运行
通过ipcs -q可以查询系统的消息队列情况
四、通过JNI创建本地共享内存
1.共享内存的基本原理
与消息队列不同,共享内存通过将/dev/shm内存区域与进程绑定,实现进程间的通信,效率更高。
2.共享内存的基本使用
3.native方法定义
public class Shm {
public native static int shmGet(int key, int size);
public native static void shmRead(int shmid, byte[] msg, int size);
public native static void shmWrite(int shmid, byte[] msg, int size,int mslen);
public native static void shmDelete(int shmid);
}
4.根据h文件编写函数的具体实现
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
#include "Shm.h"
#define SHM_SIZE 1024
/*
* Class: Shm
* Method: shmGet
* Signature: (III)I
*/
JNIEXPORT jint JNICALL Java_Shm_shmGet
(JNIEnv *env, jclass obj, jint key, jint size)
{
//key_t key = ftok(".",'a');
int shmid = -1;
shmid = shmget(key,size,IPC_CREAT | 0666);
if(shmid < 0)
{
perror("shmget");
exit(1);
}
return shmid;
}
/*
* Class: Shm
* Method: shmRead
* Signature: (I)Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_Shm_shmRead
(JNIEnv *env, jclass obj, jint shmid, jbyteArray msg, jint size)
{
//映射
char *ptr = NULL;
ptr = shmat(shmid,NULL,0);//0表示共享内存可读可写
if(ptr == (void *)-1)
{
perror("shmat");
exit(1);
}
//读数据
/*if(ptr[0] != '\0')
{
printf("ptr = %s\n",ptr);
} */
( *env ) ->SetByteArrayRegion ( env, msg, 0 , strlen(ptr), ptr ) ;
//解除共享内存映射
if(shmdt(ptr) < 0)
{
perror("shmdt");
exit(1);
}
}
/*
* Class: Shm
* Method: shmWrite
* Signature: ([B)V
*/
JNIEXPORT void JNICALL Java_Shm_shmWrite
(JNIEnv *env, jclass obj, jint shmid, jbyteArray msg, jint size, jint mslen)
{
//映射
char *ptr = NULL;
ptr = shmat(shmid,NULL,0);// 返回值为被映射的段地址
if(ptr == (void *)-1)
{
perror("shmat");
exit(1);
}
//写数据
memset(ptr,0,size);//清空内存
//char buf[128];
//memset(buf,0,sizeof(buf));
( *env ) ->GetByteArrayRegion ( env, msg, 0 , mslen, ptr ) ;
//printf("%d\n",sizeof(msg));
//printf("%s\n",ptr);
//解除共享内存映射
if(shmdt(ptr) < 0)
{
perror("shmdt");
exit(1);
}
}
/*
* Class: Shm
* Method: shmDelete
* Signature: ([B)V
*/
JNIEXPORT void JNICALL Java_Shm_shmDelete
(JNIEnv *env, jclass obj, jint shmid)
{
if(shmctl(shmid,IPC_RMID,NULL) == -1)
{
perror("shmctl");
exit(1);
}
}
5.编写测试java类
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Shm {
static {
System.load("/home/xd/Documents/java_workspace/jni/jni_lib/Shm.so");
}
public native static int shmGet(int key, int size);
public native static void shmRead(int shmid, byte[] msg, int size);
public native static void shmWrite(int shmid, byte[] msg, int size,int mslen);
public native static void shmDelete(int shmid);
public static void main(String[] args) {
int shmid = shmGet(10,1024);
// int shmid = 1179649;
System.out.println(shmid);
Scanner sc = new Scanner(System.in);
while(true){
String[] s = sc.nextLine().split(" ",2);
String s1 = s[0];
System.out.println(s1);
if(Integer.valueOf(s1)==0){
byte[] bytes1 = s[1].getBytes();
System.out.println(bytes1.length);
shmWrite(shmid,bytes1,1024,bytes1.length);
}
if(Integer.valueOf(s1)==1){
byte[] bytes = new byte[1024];
shmRead(shmid,bytes,1024);
System.out.println(new String(bytes));
}
if(Integer.valueOf(s1)==2){
shmDelete(shmid);
break;
}
}
}
}
6.编译并运行
总结
对于共享内存、消息队列以及JNI的有了一定的使用能力。更多的东西打算在实践中边用边学。