通过JNI调用C语言函数实现与linux上其他进程进行通信的共享内存和消息队列


前言

本地的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 <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.共享内存的基本使用

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
编写与C语言对应的JNI函数,用于与C代码进行交互,可以按照以下步骤进行: 1. 在Java代码中声明native方法:首先,在Java类中声明一个native方法,该方法将与C代码进行对应。使用`native`关键字来标记这个方法,告诉编译器这个方法的实现在本地代码中。 ```java public class MyNativeClass { public native void myNativeMethod(); } ``` 2. 生成头文件:在命令行或终端中,使用`javac`命令编译Java类,并使用`javah`命令生成对应的头文件。头文件将包含与C代码交互所需的JNI函数声明。 ```shell javac MyNativeClass.java javah -jni MyNativeClass ``` 这将生成一个名为`MyNativeClass.h`的头文件。 3. 实现JNI函数:打开生成的头文件`MyNativeClass.h`,其中包含了与C代码交互所需的JNI函数声明。在C源代码文件中实现这些JNI函数。 ```c #include "MyNativeClass.h" JNIEXPORT void JNICALL Java_MyNativeClass_myNativeMethod(JNIEnv *env, jobject obj) { // 在这里实现与C代码的交互逻辑 // ... } ``` 4. 编译C代码:将C源代码文件和JNI头文件一起编译成共享库(动态链接库)文件。具体的编译命令可能因操作系统和编译器而异。 ```shell gcc -c MyNativeClass.c -o MyNativeClass.o gcc -shared -o libMyNativeClass.so MyNativeClass.o ``` 这将生成一个名为`libMyNativeClass.so`的共享库文件,可供Java代码加载和使用。 5. 在Java代码中加载共享库:在Java代码中使用`System.loadLibrary()`方法加载生成的共享库,以便在运行时加载C代码。 ```java public class MyNativeClass { static { System.loadLibrary("MyNativeClass"); } public native void myNativeMethod(); } ``` 现在,你可以在Java代码中调用`myNativeMethod()`方法,它将执行与C代码实现JNI函数交互。 请注意,JNI的使用需要一定的C语言Java语言的知识。确保你熟悉JNI的基本概念和语法,以便正确地进行数据传递和交互。 希望这些信息对你有所帮助!如果你需要进一步指导,请提供更多的具体细节或代码示例,我将尽力提供更详细的建议。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值