Linux平臺(tái)下利用JNI+雙向RMI實(shí)現(xiàn)遠(yuǎn)程推送
一、 前言
作為一種優(yōu)秀的編程語(yǔ)言,Java在許多方面具有突出的優(yōu)越性。其中,RMI技術(shù)充分展現(xiàn)了Java卓越的分布式計(jì)算能力,而JNI技術(shù)則體現(xiàn)了Java結(jié)合異種編程語(yǔ)言的強(qiáng)大能力。人們常說(shuō),RMI是“從Java到Java”,這種說(shuō)法忽視了這樣一個(gè)事實(shí):Java可利用JNI技術(shù)很容易地與原有系統(tǒng)連接。JNI+RMI的技術(shù)解決方案極大地延伸了Java的分布式功能。
本文的寫作是基于這樣一種實(shí)際需要:在實(shí)際運(yùn)行環(huán)境當(dāng)中,我們需要將一臺(tái)Linux網(wǎng)絡(luò)工作站產(chǎn)生的信息實(shí)時(shí)動(dòng)態(tài)的顯示在遠(yuǎn)程監(jiān)控者的機(jī)器上,以便他及時(shí)對(duì)該Linux工作站上發(fā)生的情況進(jìn)行處理。比如, Linux工作站上運(yùn)行著網(wǎng)絡(luò)入侵檢測(cè)系統(tǒng),檢測(cè)到的入侵信息必須實(shí)時(shí)地提供給遠(yuǎn)程監(jiān)控者,以便及時(shí)作出響應(yīng);再比如, Linux工作站上運(yùn)行著網(wǎng)絡(luò)管理系統(tǒng),產(chǎn)生的有關(guān)網(wǎng)絡(luò)流量和網(wǎng)絡(luò)性能的數(shù)據(jù)也必須及時(shí)提供給網(wǎng)絡(luò)管理人員。
而Linux平臺(tái)下的軟件,大多數(shù)是用C語(yǔ)言編寫的,這是一筆難以舍棄的財(cái)富。而且C語(yǔ)言與底層系統(tǒng)的緊密結(jié)合以及C的運(yùn)行速度,是Java所不具備。這就為JNI+RMI技術(shù)提供了廣闊的舞臺(tái)。
二、 JNI技術(shù)
Java以其跨平臺(tái)的特性得到廣泛應(yīng)用,其代碼可以一次編譯多處執(zhí)行。但正是這種特性 給它帶來(lái)了一定的局限性,一些與平臺(tái)相關(guān)的功能就不能很好地支持。幸運(yùn)的是Java提供了JNI技術(shù)——完備的C語(yǔ)言接口,讓我們可以利用C語(yǔ)言的強(qiáng)大功能來(lái)彌補(bǔ)Java的不足。很容易發(fā)現(xiàn)JNI在Java和本地應(yīng)用程序之間起著膠水的作用。圖1將描述JNI是如何將應(yīng)用程序的C語(yǔ)言部分和Java部分連接在一起的。圖1來(lái)源于Sun公司的Java指南。

三、 RMI技術(shù)
1、 運(yùn)行原理
RMI 應(yīng)用程序通常包括兩個(gè)獨(dú)立的程序:服務(wù)器程序和客戶機(jī)程序。典型的服務(wù)器應(yīng)用程序?qū)?chuàng)建多個(gè)遠(yuǎn)程對(duì)象,使這些遠(yuǎn)程對(duì)象能夠被引用,然后等待客戶機(jī)調(diào)用那些遠(yuǎn)程對(duì)象上的方法。而典型的客戶機(jī)程序則從服務(wù)器中得到一個(gè)或多個(gè)遠(yuǎn)程對(duì)象的引用,然后調(diào)用遠(yuǎn)程對(duì)象的方法。RMI 為服務(wù)器和客戶機(jī)進(jìn)行通訊和信息傳遞提供了一種機(jī)制。這樣的應(yīng)用程序有時(shí)被稱為分布式對(duì)象應(yīng)用程序。分布式對(duì)象應(yīng)用程序需要:(1)定位遠(yuǎn)程對(duì)象。它既可用 RMI 的簡(jiǎn)單命名工具 rmiregistry 來(lái)注冊(cè)它的遠(yuǎn)程對(duì)象,也可將遠(yuǎn)程對(duì)象引用作為常規(guī)操作的一部分來(lái)進(jìn)行傳遞和返回。(2)與遠(yuǎn)程對(duì)象通訊。遠(yuǎn)程對(duì)象間通訊的細(xì)節(jié)由 RMI 處理,對(duì)于程序員來(lái)說(shuō),遠(yuǎn)程通訊看起來(lái)就象標(biāo)準(zhǔn)的 Java 方法調(diào)用。(3)給作為參數(shù)或返回值傳遞的對(duì)象加載類字節(jié)碼。因?yàn)?RMI 允許調(diào)用程序?qū)⒓?Java 對(duì)象傳給遠(yuǎn)程對(duì)象,所以 RMI 將提供必要的機(jī)制,既可以加載對(duì)象的代碼又可以傳輸對(duì)象的數(shù)據(jù)。服務(wù)器調(diào)用注冊(cè)服務(wù)程序以使名字與遠(yuǎn)程對(duì)象相關(guān)聯(lián)。客戶機(jī)在服務(wù)器注冊(cè)服務(wù)程序中用遠(yuǎn)程對(duì)象的名字查找該遠(yuǎn)程對(duì)象,然后調(diào)用它的方法。RMI 能用 Java系統(tǒng)支持的任何 URL 協(xié)議(例如 HTTP、FTP、file 等)加載類字節(jié)碼。
下圖描繪了一個(gè)RMI分布式應(yīng)用程序 ,它通過(guò)registry得到一個(gè)遠(yuǎn)程對(duì)象的引用。服務(wù)器調(diào)用registry將遠(yuǎn)程對(duì)象連接(或者綁定)到一個(gè)名字上。客戶端在服務(wù)器的 registry上根據(jù)那個(gè)名字查找遠(yuǎn)程對(duì)象,并且調(diào)用名字上的一個(gè)方法。圖2同時(shí)顯示出每當(dāng)需要時(shí),RMI系統(tǒng)使用一個(gè)Web服務(wù)器來(lái)裝載類字節(jié)碼,從服務(wù)器到客戶端并且從顧客到服務(wù)器。圖2來(lái)源于Sun公司的Java指南。

2、 雙向RMI技術(shù)
前面講到通常情況下在RMI應(yīng)用程序中,是服務(wù)器程序提供遠(yuǎn)程方法給客戶機(jī)程序調(diào)用。但是某種情況下,RMI應(yīng)用程序同時(shí)也需要客戶機(jī)程序提供遠(yuǎn)程方法給服務(wù)器程序調(diào)用。也就是說(shuō),某種情況下,RMI應(yīng)用程序的兩個(gè)部分同時(shí)具有服務(wù)器和客戶機(jī)的功能。
比如,你的遠(yuǎn)程客戶希望可以通過(guò)瀏覽器實(shí)時(shí)查看Linux平臺(tái)下程序運(yùn)行的產(chǎn)生的信息。那么從技術(shù)上說(shuō):遠(yuǎn)程客戶機(jī)器上運(yùn)行的Applet程序是RMI服務(wù)器程序,而Linux平臺(tái)下程序是RMI客戶機(jī)程序。Linux平臺(tái)下程序通過(guò)RMI調(diào)用Applet程序的相關(guān)方法,將信息實(shí)時(shí)推送到遠(yuǎn)程客戶機(jī)器。這是一個(gè)非常典型的RMI應(yīng)用。
但是這里出現(xiàn)了一個(gè)問(wèn)題:遠(yuǎn)程客戶機(jī)器的IP地址沒(méi)有辦法確定,遠(yuǎn)程客戶應(yīng)該可以從任何連接到Internet的機(jī)器上訪問(wèn)到這些信息。如果不知道IP地址,Linux平臺(tái)下程序不可能通過(guò)URL訪問(wèn)遠(yuǎn)程Applet程序中的RMI方法。
因此這里必須使用雙向RMI技術(shù)。首先由遠(yuǎn)程Applet程序通過(guò)URL調(diào)用Linux平臺(tái)下程序的RMI方法,建立起RMI連接。然后,再由Linux平臺(tái)下程序調(diào)用遠(yuǎn)程Applet程序中RMI來(lái)推送信息。
四、 程序?qū)嵗?nbsp;
關(guān)于Linux平臺(tái)下C程序的輸出,可以是網(wǎng)絡(luò)入侵檢測(cè)系統(tǒng),網(wǎng)絡(luò)管理系統(tǒng),或者其他系統(tǒng)。這里為了簡(jiǎn)化程序,用一個(gè)簡(jiǎn)單的C程序代替。該程序?qū)⒛阍贚inux平臺(tái)下輸入的字符串,回顯到遠(yuǎn)端客戶瀏覽器上。
1、 編寫Java文件CreatMessage.java
里面包含一些native的函數(shù),這些函數(shù)就是將在C中要實(shí)現(xiàn)的。另外程序?qū)⒙暶饕粋€(gè)調(diào)用RMI方法的Java私有方法,該方法將在C程序中被調(diào)用。源程序如下:
class CreatMessage{
MessageServerImpl ms;
//聲明一個(gè)本地方法接口函數(shù)
public native void creatmsa();
//聲明一個(gè)Java方法,該方法將在C程序中被調(diào)用
private void pushMessage(String message){
//新建一個(gè)RMI遠(yuǎn)程對(duì)象,并對(duì)其賦值
Message msa = new Message();
msa.MessageString = message;
// notifyEvent方法將調(diào)用RMI方法notifiedEvent(Message msa)
try{
ms.notifyEvent(msa);
} catch(Exception e){
System.out.println("notifyEvent Exception:"+e.getMessage());
}//catch end
}
//構(gòu)造函數(shù)負(fù)責(zé)傳遞MessageServerImpl對(duì)象的實(shí)例
public CreatMessage(MessageServerImpl msserver){
ms = msserver;
}
}
2、 編譯CreatMessage.java文件,生成C語(yǔ)言頭文件CreatMessage.h
用Javac CreatMessage.java命令編譯Java源文件,生成CreatMessage.class,再用Javah CreatMessage命令生成C語(yǔ)言頭文件CreatMessage.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class CreatMessage */
#ifndef _Included_CreatMessage
#define _Included_CreatMessage
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: CreatMessage
* Method: creatmsa
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_CreatMessage_creatmsa
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
這是由javah命令生成的文件,不需要也不允許進(jìn)行任何修改。我們注意到這里有一段程序 “JNIEXPORT void JNICALL Java_CreatMessage_creatmsa (JNIEnv *, jobject);”。Java_CreatMessage_creatmsa 提供了CreatMessage.class中本地方法的具體實(shí)現(xiàn),當(dāng)你編寫本地方法的實(shí)現(xiàn)程序時(shí),你將使用相同的方法簽名。如果CreatMessage.class含有其他的本地方法,這些方法的簽名同樣會(huì)在頭文件中出現(xiàn)。
3、 編寫creatmsa.c,實(shí)現(xiàn)本地方法
首先我們將如何得到Java方法的簽名呢?有兩種方法,一種是依據(jù)生成規(guī)則手工推算,另外Java 類文件反匯編程序工具javap ,可以幫助你消除手工推算方法簽名時(shí)發(fā)生的錯(cuò)誤。你能使用javap工具為指定的類打印出成員變量和方法簽名:javap -s -p CreatMessage。
要在C程序中調(diào)用Java方法,需要完成下面三個(gè)步驟:
(1) 在C程序中調(diào)用JNI方法GetObjectClass,它將返回該Java對(duì)象的Java類對(duì)象。
(2) 接著調(diào)用JNI方法GetMethodID,它將在一個(gè)給定的類文件中查找Java方法。該查找是基于方法名字以及方法簽名。如果該方法不存在,GetMethodID將返回0。程序?qū)⒘⒓捶祷兀伋鯪oSuchMethodError。
(3) 將調(diào)用JNI方法CallVoidMethod。該方法將激活一個(gè)返回值為void的實(shí)例方法。你必須將object, method ID以及該方法的參數(shù)傳遞給CallVoidMethod。
creatmsa.c源程序如下:
#include <stdio.h>
#include <jni.h>
#include "CreatMessage.h"
static jclass cls ;
static jmethodID mid ;
JNIEXPORT void JNICALL Java_CreatMessage_creatmsa(JNIEnv *env, jobject obj)
{
char buff[128];
jstring message;
//將用戶輸入字符串,賦值給buff
scanf("%s", buff);
//將buff的值轉(zhuǎn)換成UTF格式,并賦給message
message = (*env)->NewStringUTF(env,buff);
cls = (*env)->GetObjectClass(env, obj);
mid = (*env)->GetMethodID(env, cls, "pushMessage", "(Ljava/lang/String;)V");
if (mid == 0){
printf("GetMethodID error");
return;
}
(*env)->CallVoidMethod(env, obj, mid, message);
}
4、 編譯creatmsa.c,生成libcreatmsa.so
在Linux平臺(tái)下編譯creatmsa.c,要格外注意路徑問(wèn)題。最經(jīng)常出現(xiàn)的錯(cuò)誤就是因?yàn)槁窂皆O(shè)置不對(duì),而引起的無(wú)法找到編譯所需要的文件。本文中使用jdk1.3.1的目錄為/url/local/j2sdk1.3.1,所有例子程序都存放在/rmitest目錄下。因此本文路徑設(shè)置為:
classpath = /rmitest:
/url/local/j2sdk1.3.1/include:
/url/local/j2sdk1.3.1/jre/lib/i386
export classpath
LD_LIBRARY_PATH = /rmitest:
/url/local/j2sdk1.3.1/jre/lib/i386:
/url/local/j2sdk1.3.1/jre/lib/i386/native_threads:
/url/local/j2sdk1.3.1/jre/lib/i386/classic:
$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
設(shè)置完路徑以后,就可以開(kāi)始編譯程序了,使用命令:
gcc -I//url/local/j2sdk1.3.1/include
-I//url/local/j2sdk1.3.1/include/linux
-shared
creatmsa.c -o libcreatmsa.so
5、 Message.java
在RMI分布式應(yīng)用系統(tǒng)中,服務(wù)器與客戶機(jī)之間傳遞的Java對(duì)象必須是可序列化的對(duì)象。不可序列化的對(duì)象不能在對(duì)象流中進(jìn)行傳遞。因此,我們必須生成一個(gè)Java類以傳遞參數(shù)。這個(gè)類定義的很簡(jiǎn)單,在實(shí)際運(yùn)用中,可以根據(jù)需要增加內(nèi)容。
import java.io.Serializable;
import java.io.*;
public class Message implements Serializable
{
String MessageString;
}
6、 MessageServer.java
定義兩個(gè)RMI接口。它們將在MessageServerImpl.java程序中實(shí)現(xiàn)。
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MessageServer extends Remote {
void newClient(MessageClient mc) throws RemoteException;
void removeClient(MessageClient mc) throws RemoteException;
}
7、 MessageServerImpl.java
實(shí)現(xiàn)在MessageServer.java程序中定義的RMI接口。
import java.rmi.*;
import java.rmi.server.*;
import java.util.Vector;
public class MessageServerImpl extends UnicastRemoteObject implements MessageServer
{
Vector clients=new Vector();
public MessageServerImpl() throws RemoteException {
super();
}
public void newClient(MessageClient mc) throws RemoteException {
clients.addElement(mc);
}
public void removeClient(MessageClient mc) throws RemoteException {
clients.removeElement(mc);
}
public void notifyEvent(Message msa) throws RemoteException {
Vector tc=(Vector)clients.clone();
for (int i=0;i<tc.size();i++){
try {
((MessageClient)(tc.elementAt(i))).notifiedEvent(msa);
} catch(Exception e){
removeClient((MessageClient)(tc.elementAt(i)));
}
}
}
}
8、 MessageClient.java
定義兩個(gè)RMI接口。它們將在MessageClientImpl.java程序中實(shí)現(xiàn)。
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MessageClient extends Remote {
void notifiedEvent(Message msa) throws RemoteException;
}
9、 MessageClientImpl.java
實(shí)現(xiàn)在MessageClient.java程序中定義的RMI接口。
import java.rmi.*;
import java.rmi.server.*;
import javax.swing.*;
import java.awt.*;
public class MessageClientImpl extends UnicastRemoteObject implements MessageClient
{
JTextArea jt;
static String ipAddr="255.255.255.255";
public void notifiedEvent(Message msa) throws RemoteException {
jt.insert(msa.MessageString+"\n",0);
}
public MessageClientImpl(JTextArea jtext) throws RemoteException {
super();//調(diào)用其父類的構(gòu)造函數(shù)UnicastRemoteObject
jt=jtext;
}
public void init() throws RemoteException{
try {
String name = "http://"+ipAddr+":1099/MessageServer";
MessageServer fs = (MessageServer)Naming.lookup(name);
fs.newClient(this);
} catch(Exception e){
System.err.println("MessageClient exception: " + e.getMessage());
}
}
public void setIPaddr(String str)
{
ipAddr = str;
}
}
10、 編譯生成Stub文件
利用命令rmic -v1.2 MessageClientImpl和命令rmic -v1.2 MessageServerImpl
分別生成MessageClientImpl_Stub.class文件和MessageServerImpl_Stub.class文件
11、 StartServer.java
編寫StartServer.java文件,初始化RMI運(yùn)行環(huán)境如啟動(dòng)rmigegistry,設(shè)置RMISecurityManager,綁定RMI對(duì)象等等。并加栽C程序生成的動(dòng)態(tài)連接庫(kù)文件libcreatmsa.so。
import java.rmi.*;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
public class StartServer{
static {
System.loadLibrary("creatmsa");/*actual name is "libcreatmsa.so"*/
}
public static void main(String args[])
{
if(System.getSecurityManager() == null)
System.setSecurityManager(new RMISecurityManager());
try{
LocateRegistry.createRegistry(1099);
//注冊(cè)IDS處理服務(wù)器
MessageServerImpl msObj = new MessageServerImpl();
Naming.rebind("http://127.0.0.1:1099/MessageServer",msObj);
//啟動(dòng)c程序
CreatMessage cm = new CreatMessage(msObj);
cm.creatmsa();
} catch (Exception e){
System.out.println("registe error: " + e.getMessage());
}
}
}
12、 MessageEcho.java
編寫遠(yuǎn)程客戶端的Applet程序,調(diào)用RMI方法,與服務(wù)器建立連接。并一個(gè)Panel上顯示Linux平臺(tái)下程序產(chǎn)生的數(shù)據(jù)。
import java.awt.*;
import java.applet.Applet;
import javax.swing.*;
import java.rmi.RMISecurityManager;
public class MessageEcho extends JApplet{
Container contentPane = getContentPane();
JPanel panel = new JPanel(new BorderLayout());
JPanel alarmPanel;//顯示Linux平臺(tái)下程序產(chǎn)生的數(shù)據(jù)的模板
//顯示Linux平臺(tái)下程序產(chǎn)生的數(shù)據(jù)的區(qū)域
final JTextArea alarmList=new JTextArea(8,40);
//運(yùn)行Linux平臺(tái)下程序的機(jī)器的IP地址
String ipServer;
public void init() {
ipServer = getParameter("AppServer"); //此項(xiàng)在test.html中賦值
alarmPanel=new JPanel();
alarmList.insert("\nSome message here"+"\n",0);
alarmPanel.setLayout(null);
alarmList.setLineWrap(true);
alarmList.setWrapStyleWord(true);
JScrollPane areaScrollPane = new JScrollPane(alarmList);
areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
areaScrollPane.setPreferredSize(new Dimension(200, 120));
alarmPanel.add(areaScrollPane);
areaScrollPane.setBounds(0,40,200,120);
panel.add(alarmPanel,BorderLayout.CENTER);
contentPane.add(panel);
try{
MessageClientImpl mci=new MessageClientImpl(alarmList);
mci.setIPaddr(ipServer);
mci.init();
} catch (Exception e) {
System.err.println("alarmListener exception: " + e.getMessage());
e.printStackTrace();
}
}
}
13、 Test.html
在HTML文件中調(diào)用Applet的類文件。其中AppServer是Linux工作站的IP地址。
<HTML>
<BODY>
<COMMENT>
<EMBED width="750" height="400"
align="baseline" code="MessageEcho.class" codebase="."
AppServer="202.114.33.87" ><!--應(yīng)用服務(wù)器地址,即Linux工作站的IP地址-->
</EMBED>
</BODY>
</HTML>
14、 修改java.policy文件
為了允許客戶程序同RMI注冊(cè)程序和服務(wù)器對(duì)象連接,你需要提供一個(gè)策略文件(policy file)。策略文件是非常復(fù)雜的問(wèn)題。這里只提供一個(gè)修改的樣本,它賦予程序絕大多數(shù)的操作權(quán)限。這樣使你調(diào)試類似程序時(shí)候非常方便,但是也請(qǐng)注意這種做法給你的計(jì)算機(jī)帶來(lái)的潛在危險(xiǎn)。希望進(jìn)一步了解安全策略文件的讀者可以看看參考文獻(xiàn)8。
java.policy文件內(nèi)容如下:
grant {
permission java.security.AllPermission;
};
因?yàn)楸疚闹惺褂玫氖请p向RMI,所以你必須同時(shí)修改兩臺(tái)機(jī)器的策略文件。在RedHat7.1中需要將java.policy文件拷貝到/url/local/j2sdk1.3.1/jre/lib/security目錄,在Windows2000中是c:\j2sdk1.3.1\jre\lib\security目錄。當(dāng)然你需要根據(jù)你機(jī)器上的java目錄做相應(yīng)調(diào)整。
五、 實(shí)現(xiàn)環(huán)境以及運(yùn)行步驟
一臺(tái)機(jī)器操作系統(tǒng)為RedHat7.1,文件有:MessageServer.class,MessageServerImpl.class,MessageServerImpl_Stub.class,Message.class,MessageClient.class,MessageClientImpl_Stub.class,libcreatmsa.so,所有文件均在同一目錄下面。
一臺(tái)機(jī)器操作系統(tǒng)為Windows2000,文件有:MessageClient.class,MessageClientImpl.class,MessageClientImpl_Stub.class,Message.class,MessageServer.class,MessageServerImpl_Stub.class,MessageEcho.class,test.html,所有文件均在同一目錄下面。
1、 首先修改兩臺(tái)機(jī)器上的java.policy文件;
2、 然后在RedHat7.1的機(jī)器上設(shè)置路徑,即運(yùn)行上文提到的相應(yīng)命令;
3、 然后在RedHat7.1的機(jī)器上運(yùn)行命令:java StartServer;
4、 然后在Windows2000的機(jī)器上運(yùn)行命令:appletviewer test.html;
5、 接著在RedHat7.1的機(jī)器上隨意輸入一字符傳后敲回車鍵,該字符串將回顯在Windows2000機(jī)器的Appletviewer窗口中
posted on 2005-11-06 15:34 春雷的博客 閱讀(1730) 評(píng)論(1) 編輯 收藏