
本文共 10930 字,大约阅读时间需要 36 分钟。
JDBC实践
前言
本地的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/* Header for class JNIDemo */#ifndef _Included_JNIDemo#define _Included_JNIDemo#ifdef __cplusplusextern "C" { #endif/* * Class: JNIDemo * Method: sayHello * Signature: ()V */JNIEXPORT int JNICALL Java_JNIDemo_sayHello (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
编写C文件
#include#include #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:这个变量的域ID4.修改实例域的变量值
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#include #include #include #include #include #include #include #include #include #define MSG_MAX 8192#define mslen 1024struct 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.共享内存的基本使用
https://editor.csdn.net/md/?articleId=116401390
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#include #include #include #include #include #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的有了一定的使用能力。更多的东西打算在实践中边用边学。
发表评论
最新留言
关于作者
