java客户端和c服务端通信(JNI+UDP)

此博客用到JNI+UDP
最近在和某研究所联调程序,遇到很多问题,目前解决大半,快来总结一下。
题外话:程序员千万别去研究所,因为那里气氛比殡仪馆还压抑。

大致需求:
1. 某研究所提供仿真软件(C编写),可以通过udp访问执行,并通过udp返回结果。传递参数皆为结构体(C struct)
2. 利用java web编写网站,实现调用仿真软件并展现结果。


看到这个需求,认为简单的udp就可以实现。但是参数的传递将是难题。

先来看下java udp实现

/**      
 * UDP服务类.      
 * @author liukang
 */  
public class UdpServerSocket {  
    private byte[] buffer = new byte[1024];  
      
    private DatagramSocket ds = null;  
  
    private DatagramPacket packet = null;  
  
    private InetSocketAddress socketAddress = null;  
  
    private String orgIp;  
  
    /** 
     * 构造函数,绑定主机和端口. 
     * @param host 主机 
     * @param port 端口 
     * @throws Exception 
     */  
    public UdpServerSocket(String host, int port) throws Exception {  
        socketAddress = new InetSocketAddress(host, port);  
        ds = new DatagramSocket(socketAddress);  
        System.out.println("服务端启动!");  
    }  
      
    public final String getOrgIp() {  
        return orgIp;  
    }  
  
    /** 
     * 设置超时时间,该方法必须在bind方法之后使用. 
     * @param timeout 超时时间 
     * @throws Exception 
     */  
    public final void setSoTimeout(int timeout) throws Exception {  
        ds.setSoTimeout(timeout);  
    }  
  
    /** 
     * 获得超时时间. 
     * @return 返回超时时间. 
     * @throws Exception 
     */  
    public final int getSoTimeout() throws Exception {  
        return ds.getSoTimeout();  
    }  
  
    /** 
     * 绑定监听地址和端口. 
     * @param host 主机IP 
     * @param port 端口 
     * @throws SocketException 
     */  
    public final void bind(String host, int port) throws SocketException {  
        socketAddress = new InetSocketAddress(host, port);  
        ds = new DatagramSocket(socketAddress);  
    }  
  
  
    /** 
     * 接收数据包,该方法会造成线程阻塞. 
     * @return 返回接收的数据串信息 
     * @throws IOException  
     * @throws ClassNotFoundException 
     */  
    public final byte[] receive() throws IOException, ClassNotFoundException {  
        packet = new DatagramPacket(buffer, buffer.length);  
        ds.receive(packet);  
        orgIp = packet.getAddress().getHostAddress(); 
        System.out.println(orgIp);
        return buffer; 
    }  
  
    /** 
     * 将响应包发送给请求端. 
     * @param bytes 回应报文 
     * @throws IOException 
     */  
    public final void response(String info) throws IOException {  
        System.out.println("客户端地址 : " + packet.getAddress().getHostAddress()  
                + ",端口:" + packet.getPort());  
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length, packet  
                .getAddress(), packet.getPort());  
        dp.setData(info.getBytes());  
        ds.send(dp);  
    }  
  
    /** 
     * 设置报文的缓冲长度. 
     * @param bufsize 缓冲长度 
     */  
    public final void setLength(int bufsize) {  
        packet.setLength(bufsize);  
    }  
  
    /** 
     * 获得发送回应的IP地址. 
     * @return 返回回应的IP地址 
     */  
    public final InetAddress getResponseAddress() {  
        return packet.getAddress();  
    }  
  
    /** 
     * 获得回应的主机的端口. 
     * @return 返回回应的主机的端口. 
     */  
    public final int getResponsePort() {  
        return packet.getPort();  
    }  
  
    /** 
     * 关闭udp监听口. 
     */  
    public final void close() {  
        try {  
            ds.close();  
        } catch (Exception ex) {  
            ex.printStackTrace();  
        }  
    }  
  
} 
package com.platform.net;

import java.io.*;  
import java.net.*;  
import java.nio.ByteOrder;

import struct.JavaStruct;

import com.platform.report.receive.OneSend;
import com.platform.report.receive.Response1;
import com.platform.report.send.DATA1;
import com.platform.report.send.Struct1;
import com.platform.util.ByteUtil;
  
/**    
 * UDP客户端程序,用于对服务端发送数据,并接收服务端的回应信息. 
 * @author liukang
 */  
public class UdpClientSocket {  
	
	//接收buffer
    private byte[] buffer = new byte[1200];  
  
    private DatagramSocket ds = null;  
  
    /** 
     * 构造函数,创建UDP客户端 
     * @throws Exception 
     */  
    public UdpClientSocket() throws Exception {  
        //ds = new DatagramSocket();  
        ds = new DatagramSocket(21168);
    }  
      
    /** 
     * 设置超时时间,该方法必须在bind方法之后使用. 
     * @param timeout 超时时间 
     * @throws Exception 
     */  
    public final void setSoTimeout(final int timeout) throws Exception {  
        ds.setSoTimeout(timeout);  
    }  
  
    /** 
     * 获得超时时间. 
     * @return 返回超时时间 
     * @throws Exception 
     */  
    public final int getSoTimeout() throws Exception {  
        return ds.getSoTimeout();  
    }  
  
    public final DatagramSocket getSocket() {  
        return ds;  
    }  
  
    /** 
     * 向指定的服务端发送数据信息. 
     * @param host 服务器主机地址 
     * @param port 服务端端口 
     * @param bytes 发送的数据信息 
     * @return 返回构造后俄数据报 
     * @throws IOException 
     */  
    public final DatagramPacket send(final String host, final int port,  
            final byte[] bytes) throws IOException { 
    	
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress  
                .getByName(host), port); 
        
        ds.send(dp);  
        return dp;  
    }  
  
    /** 
     * 接收从指定的服务端发回的数据. 
     * @param lhost 服务端主机 
     * @param lport 服务端端口 
     * @return 返回从指定的服务端发回的数据. 
     * @throws Exception 
     */  
    public final byte[] receive(final String lhost, final int lport)  
            throws Exception {  
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length);  
        ds.receive(dp);  
        //String info = new String(dp.getData(), 0, dp.getLength());  
        return buffer;  
    }  
  
    /** 
     * 关闭udp连接. 
     */  
    public final void close() {  
        try {  
            ds.close();  
        } catch (Exception ex) {  
            ex.printStackTrace();  
        }  
    }  
    
} 

udp的问题解决了,那么难点就在与如何传值。从上面代码可以看出,udp传值都是利用byte数组。

但是如何拼装出对应的byte数组却成了问题。

熟悉网络通信的同学可能知道,网络传输字节存在大端小端问题,并且网络字节序可能和存储字节序不同的问题。

大端小端模式 http://blog.csdn.net/hackbuteer1/article/details/7722667

让java去操作字节有点勉为其难,首先看一下java各类型所占的字节数:

Int: 4 字节Short: 2字节Long: 8字节Byte: 1字节Char: 2字节Float: 4字节Double: 8字节

C数据类型和所占字节数:

16位编译器

char :1个字节 char*(即指针变量): 2个字节 short int : 2个字节
int:  2个字节 unsigned int : 2个字节 float:  4个字节
double:   8个字节 long:   4个字节 long long:  8个字节
unsigned long:  4个字节

32位编译器

char :1个字节 char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节 int:  4个字节 unsigned int : 4个字节
float:  4个字节 double:   8个字节 long:   4个字节long long:  8个字节 unsigned long:  4个字节
64位编译器

char :1个字节 char*(即指针变量): 8个字节 short int : 2个字节 int:  4个字节
unsigned int : 4个字节 float:  4个字节 double:   8个字节 long:   8个字节long long:  8个字节 unsigned long:  8个字节

对于不同编译器的C每种类型的数据所占字节数不同,在不考虑高低字节的问题,如果用java编写扩展性不好,且需要逐个字节去对齐。
遇到这个问题,想到两种方法。
一种是文件缓存,利用文件做数据交换的媒介,例如webservice之间利用xml调用
另一种方式就是不用java做通信这件事情,而是用C,让C和C之间通信,如果更换C编译器只需要重新编译动态链接库即可。
第一种实现更简单,但研究所不同意更改任何一行代码。这就是说好的合作!
JNI定义方法:
1.首先定义java类如下:注意加载动态链接库
public class Model {
	static{
		System.load("/SimulationPlatform/src/main/java/com/platform/jni/model.dylib");
	}
	
	public native void model1(DATA1 data);
	
}
2.javac编译形成class文件
javac -classpath ~/Documents/phpworkspace/SimulationPlatform/src/main/java/ Model.java
3.javah形成c语言头文件
javah -classpath ~/Documents/phpworkspace/SimulationPlatform/src/main/java/ com.platform.jni.Model
4.编写c语言程序,各类型如何转换都在程序上
gcc -fPIC -D\_REENTRANT -I /Library/Java/JavaVirtualMachines/jdk1.7.0_75.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.7.0_75.jdk/Contents/Home/include/darwin -c model.c
JNIEXPORT void JNICALL Java_com_platform_jni_Model_model1
  (JNIEnv *env, jobject obj1, jobject obj2){
  	struct DATA1 data2;
	int len;
	int i,j;

	jclass class = (*env)->GetObjectClass(env,obj2);//获得class
	printf("get class right\n");
	/*获取char开始 s1*/
	jfieldID s1id = (*env)->GetFieldID(env,class,"s1","C");
	jchar s1 = (*env)->GetCharField(env,obj2,s1id);
	data2.s1 = s1-'0';
	/*获取char结束*/

	/*获取float开始 speed*/
	jfieldID speedid = (*env)->GetFieldID(env,class,"speed","F");
	jfloat speed = (*env)->GetFloatField(env,obj2,speedid);
	data2.speed = speed;
	printf("%f\n",speed);
	/*获取float结束*/

	/*获取float开始 ang*/
	jfieldID angid = (*env)->GetFieldID(env,class,"ang","F");
	jfloat ang = (*env)->GetFloatField(env,obj2,angid);
	data2.ang = ang;
	printf("%f\n",ang);
	/*获取float结束*/

	/*获取float开始 fre*/
	jfieldID freid = (*env)->GetFieldID(env,class,"fre","F");
	jfloat fre = (*env)->GetFloatField(env,obj2,freid);
	data2.fre = fre;
	printf("%f\n",fre);
	/*获取float结束*/

	/*获取float开始 bre*/
	jfieldID breid = (*env)->GetFieldID(env,class,"bre","F");
	jfloat bre = (*env)->GetFloatField(env,obj2,breid);
	data2.bre = bre;
	printf("%f\n",bre);
	/*获取float结束*/

	/*获取float开始 cre*/
	jfieldID creid = (*env)->GetFieldID(env,class,"cre","F");
	jfloat cre = (*env)->GetFloatField(env,obj2,creid);
	data2.cre = cre;
	printf("%f\n",cre);
	/*获取float结束*/

	/*获取float开始 distence*/
	jfieldID distenceid = (*env)->GetFieldID(env,class,"distence","F");
	jfloat distence = (*env)->GetFloatField(env,obj2,distenceid);
	data2.distence = distence;
	printf("%f\n",distence);
	/*获取float结束*/

	/*获取float开始 ang1*/
	jfieldID ang1id = (*env)->GetFieldID(env,class,"ang1","F");
	jfloat ang1 = (*env)->GetFloatField(env,obj2,ang1id);
	data2.ang1 = ang1;
	printf("%f\n",ang1);
	/*获取float结束*/

	/*获取float开始 ang2*/
	jfieldID ang2id = (*env)->GetFieldID(env,class,"ang2","F");
	jfloat ang2 = (*env)->GetFloatField(env,obj2,ang2id);
	data2.ang2 = ang2;
	printf("%f\n",ang2);
	/*获取float结束*/

	/*获取float开始 time*/
	jfieldID timeid = (*env)->GetFieldID(env,class,"time","F");
	jfloat ftime = (*env)->GetFloatField(env,obj2,timeid);
	data2.time = ftime;
	printf("%f\n",ang2);
	/*获取float结束*/

	/*获取float开始 cy1*/
	jfieldID cy1id = (*env)->GetFieldID(env,class,"cy1","F");
	jfloat cy1 = (*env)->GetFloatField(env,obj2,cy1id);
	data2.cy1 = cy1;
	printf("%f\n",cy1);
	/*获取float结束*/

	/*获取float开始 ss*/
	jfieldID ssid = (*env)->GetFieldID(env,class,"ss","F");
	jfloat ss = (*env)->GetFloatField(env,obj2,ssid);
	data2.ss = ss;
	printf("%f\n",ss);
	/*获取float结束*/

	/*获取float开始 ang3*/
	jfieldID ang3id = (*env)->GetFieldID(env,class,"ang3","F");
	jfloat ang3 = (*env)->GetFloatField(env,obj2,ang3id);
	data2.ang3 = ang3;
	printf("%f\n",ang3);
	/*获取float结束*/

	/*获取float开始 ang4*/
	jfieldID ang4id = (*env)->GetFieldID(env,class,"ang4","F");
	jfloat ang4 = (*env)->GetFloatField(env,obj2,ang4id);
	data2.ang4 = ang4;
	printf("%f\n",ang4);
	/*获取float结束*/

	/*获取char开始 type1*/
	jfieldID type1id = (*env)->GetFieldID(env,class,"type1","C");
	jchar type1 = (*env)->GetCharField(env,obj2,type1id);
	data2.type1 = type1-'0';
	/*获取char结束*/

	/*获取char开始 type2*/
	jfieldID type2id = (*env)->GetFieldID(env,class,"type2","C");
	jchar type2 = (*env)->GetCharField(env,obj2,type2id);
	data2.type2 = type2-'0';
	/*获取char结束*/

	/*获取char开始 type3*/
	jfieldID type3id = (*env)->GetFieldID(env,class,"type3","C");
	jchar type3 = (*env)->GetCharField(env,obj2,type3id);
	data2.type3 = type3-'0';
	/*获取char结束*/

	/*获取char开始 len1*/
	jfieldID len1id = (*env)->GetFieldID(env,class,"len1","C");
	jchar len1 = (*env)->GetCharField(env,obj2,len1id);
	data2.len1 = len1-'0';
	/*获取char结束*/

	/*获取char开始 len2*/
	jfieldID len2id = (*env)->GetFieldID(env,class,"len2","C");
	jchar len2 = (*env)->GetCharField(env,obj2,len2id);
	data2.len2 = len2-'0';
	/*获取char结束*/

	/*获得char数组开始 file1*/
	jfieldID file1id = (*env)->GetFieldID(env,class,"file1","[C");//获得属性
	jcharArray  file1array = (jcharArray)(*env)->GetObjectField(env,obj2,file1id);//获得参数值
	len = (*env)->GetArrayLength(env,file1array);
	jchar file1[len];//char数组
	(*env)->GetCharArrayRegion(env,file1array,0,len,file1);
    for( i = 0 ;i<len;i++){
            printf("%c\n",file1[i]);
            data2.file1[i] = file1[i];
    }
	/*获得char数组结束*/

    /*获得char数组开始 file2*/
	jfieldID file2id = (*env)->GetFieldID(env,class,"file2","[C");//获得属性
	jcharArray  file2array = (jcharArray)(*env)->GetObjectField(env,obj2,file2id);//获得参数值
	len = (*env)->GetArrayLength(env,file2array);
	jchar file2[len];//char数组
	(*env)->GetCharArrayRegion(env,file2array,0,len,file2);
    for( i = 0 ;i<len;i++){
            printf("%c\n",file2[i]);
            data2.file2[i] = file2[i];
    }
	/*获得char数组结束*/

	/**发送开始**/

	struct sockaddr_in out;

	memset(&out,0,sizeof(out));
	out.sin_family = AF_INET;
	out.sin_port  = htons(PORT);
	out.sin_addr.s_addr = inet_addr(IP);


	int s;

	len = sizeof(struct sockaddr_in);
	s = socket(AF_INET,SOCK_DGRAM,0);

	if(s == -1){
	printf("can not create socket\n");
	}

	int flag = sendto(s,(char*)&data2,sizeof(data2),0,(struct sockaddr *)&out,len);
	if(flag == -1){
	printf("socket wrong!\n");
	}
	close(s);
	/**发送结束**/
  }

5.形成动态链接库,我用的是Mac所以形成dylib
gcc -shared model.o -o model1.dylib


动态链接库形成之后,就可以通过调用native方法来发送数据。再通过java接收即可。



版权声明:本文为liukanglucky原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。