通过JNI调用C语言函数实现与linux上其他进程进行通信的共享内存和消息队列
发布日期:2021-05-07 09:37:14 浏览次数:19 分类:精选文章

本文共 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。

IDEA生成头文件

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:这个变量的域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 
#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的有了一定的使用能力。更多的东西打算在实践中边用边学。

上一篇:System V共享内存的基本使用与实现
下一篇:小米商城项目分析(上)

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2025年03月27日 06时01分35秒