蔣仕勇
摘要:提出了在Client/Server架構下,根據數據庫的數據變化來驅動遠程客戶端應用程序的設計方案;該方案基于Java 、Socket、JNI、IPC以及Windows消息機制等技術,克服了傳統(tǒng)對數據庫定時掃描的缺點,文章最后給出了基于PowerBuilder客戶端應用程序的一個具體實現。
關鍵詞:Socket IPC;JNI;Windows消息機制;Java
1、問題的提出
當基于集中式Client/Server架構的應用系統(tǒng)需要根據數據庫中數據的變化情況來決定是否啟動相應的處理程序時,以往采用了由應用程序定時對數據庫進行掃描的方式。這種定時的由客戶端對數據庫服務器進行掃描的方法有如下缺點:
①造成服務器的負荷增大,特別是當定時掃描在數據操作頻繁時進行,還造成服務器性能下降;
②網絡流量增多;
③定時掃描并不能實時的處理隨機出現的數據變化,造成處理滯后。特別是對于數據更新無規(guī)律的應用,時間間隔的設定更是困難,因為時間間隔設定過大,會降低數據的實時性,時間間隔設定過小,又增加了網絡負擔和服務器負荷。
但目前主流數據庫都沒有提供當數據庫數據發(fā)生變化時及時通知客戶端應用的直接方法。
2、解決方案的設計思想
針對以上問題,我們提出了一種當數據庫信息發(fā)生變化時能及時通知遠程客戶端應用程序的解決方案,克服了由遠程客戶端應用程序定時掃描數據庫的弊端。該解決方案的核心思想是:利用數據庫的觸發(fā)器機制,在數據發(fā)生變化后調用存儲過程,然后在存儲過程中調用Java程序,Java程序實現Socket通信,在客戶端利用消息機制將數據變化的消息通知應用程序,應用程序收到消息后觸發(fā)相應事件執(zhí)行相應操作,如圖1。
2.1數據庫的觸發(fā)機制
大型關系數據庫都提供了觸發(fā)器(Trigger)機制,ORACLE數據庫觸發(fā)器定義了當一些數據庫相關事件發(fā)生時數據庫應采取的動作。觸發(fā)器可用于完整性控制,審計表中的數據變化或者監(jiān)控數據的變動等。觸發(fā)器體由PL/SQL代碼塊組成。
2.2由存儲過程調用Java類訪問外部資源
由于整個應用系統(tǒng)的整體架構是基于集中式Client/Server模式,客戶端編程語言是非Java的,Oracle數據庫中沒有提供直接訪問外部資源的機制,但提供了對Java的支持,可利用LoadJava方法,將Java程序裝載到Oracle數據庫中作為數據對象供存儲過程調用,利用Java程序訪問外部資源。
2.3通過Java程序實現服務器與遠程客戶端的Socket通訊
當數據變化達到一定條件后,Oracle通過Socket通知遠程客戶端,遠程客戶端的Socket服務程序通過JNI調用C語言的動態(tài)鏈接庫,獲取本地非Java應用程序的進程信息,利用Windows的消息機制。將Oracle的控制信息傳入非Java應用程序,由非Java應用程序響應消息,觸發(fā)相應的事件作出相應的處理,從而達到數據驅動遠程客戶端應用的目的。
3、關鍵技術
3.1基于Java的Socket通訊
利用TCP/IP協(xié)議在客戶機和服務器之間建立Socket連接是網絡通訊的一種模式,這種通訊模式首先分別在客戶機和服務器端創(chuàng)建Socket,并建立一個可靠的Socket連接,然后雙方在這個Socket連接上進行數據交互。使用Socket進行遠程通訊的方式有3種:
① 字節(jié)流套接字(StreamSocket):TCP/IP協(xié)議族中TCP協(xié)議使用此類接口,它提供面向連接的 (建立虛電路 )、無差錯的、發(fā)送順序一致的、包長度不限和非重復的網絡信包傳輸;
② 數據報套接字(DatagramSocket):TCP/IP協(xié)議族中的UDP(User Datagram Protocol)協(xié)議使用此類接口,它是無連接的服務,以獨立的信包進行網絡傳輸,信包最大長度為 32KB,傳輸不保證順序性、可靠性和無重復性,通常用于單個報文傳輸或可靠性要求不高的場合;
③ 原始數據包套接字(Raw Socket):提供對網絡下層通信協(xié)議 (如IP協(xié)議 )的直接訪問,一般不是提供給普通用戶的。主要用于開發(fā)新的協(xié)議或用于提取協(xié)議較隱蔽的功能。
其中字節(jié)流套接字是最常用的套接字類型。在Java語言中利用java.net包中的Socket類和ServerSocket類創(chuàng)建客戶端和服務端Socket。在服務端采用了多線程技術使得服務端的Socket能同時為多個客戶端請求服務,極大的提高了運行效率。
3.2 JNI調用
由于客戶端應用程序采用的開發(fā)工具不同,例如PowerBuilder、VisualBasic等,要獲取正在運行中的非Java應用程序的信息,由于Java又沒有提供諸如指針等概念,因此借助C語言來獲取應用程序的進程信息、以及利用windows的消息機制向非Java應用程序傳遞消息,來實現對非Java應用程序的控制,而Java調用C語言需要用到JNI接口標準。JNI(Java Native Interface)是Java與其他編程語言的集成編程接口,又稱為本地方法接口。它使運行于Java虛擬機上的Java代碼與其它語言編寫的庫和應用程序能夠互相調用。JNI允許本地方法建立、使用和更新Java對象,調用Java方法和引用Java類。JNI也允許Java代碼調用C、C++等語言編寫的程序和庫。Invocation API(JNI 的一部分)可以用來將 Java 虛擬機(JVM)嵌入到本機應用程序中,從而允許程序員從本機代碼內部調用 Java 代碼。由于在運行環(huán)境(Runtime)下,只有動態(tài)鏈接庫或者共享對象庫能夠被Java虛擬機引導,而靜態(tài)庫和壓縮庫不能在運行環(huán)境下被調用。所以在Java中調用其它編程語言生成的代碼是通過調用動態(tài)鏈接庫或者共享對象庫的方式來實現的,而在其它語言中調用Java對象是通過引用指向Java對象的指針來實現的。
3.3 消息機制
Windows消息提供了應用程序與應用程序之間、應用程序與Windows系統(tǒng)之間進行通訊的手段[ 3]。應用程序要實現的功能由消息來觸發(fā),并靠對消息的響應和處理來完成。Windows系統(tǒng)中有兩種消息隊列,一種是系統(tǒng)消息隊列,另一種是應用程序消息隊列。計算機的所有輸入設備由 Windows監(jiān)控,當一個事件發(fā)生時,Windows先將輸入的消息放入系統(tǒng)消息隊列中,然后再將輸入的消息拷貝到相應的應用程序隊列中,應用程序中的消息循環(huán)從它的消息隊列中檢索每一個消息并發(fā)送給相應的窗口函數中。一個事件的發(fā)生,到達處理它的窗口函數必須經歷上述過程。消息隊列中消息的結構(MSG)為:
typedef struct tagMSG{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
其中第一個成員變量是用以標識接收消息的窗口的窗口句柄;第二個參數便是消息標識號,如WM_PAINT;第三個和第四個參數的具體意義同message值有關,均為消息參數。前四個參數是非常重要和經常用到的,至于后兩個參數則分別表示郵寄消息的時間和光標位置(屏幕坐標)。把消息傳送到應用程序有兩種方法:一種是由系統(tǒng)將消息“郵寄(post)”到應用程序的“消息隊列”這是“進隊消息”Win32 API有對應的函數:PostMessage(),此函數不等待該消息處理完就返回;而另一種則是由系統(tǒng)在直接調用窗口函數時將消息"發(fā)送(send)"給應用程序的窗口函數,屬于“不進隊消息”對應的函數是SendMessage()其必須等待該消息處理完后方可返回。
Windows 應用程序創(chuàng)建的每個窗口都在系統(tǒng)核心注冊一個相應的窗口函數,窗口函數程序代碼形式上是一個巨大的switch 語句,用以處理由消息循環(huán)發(fā)送到該窗口的消息,窗口函數由Windows 采用消息驅動的形式直接調用,而不是由應用程序顯示調用的,窗口函數處理完消息后又將控制權返回給Windows。
4、解決方案的實現
4.1 基于Java的Socket通訊服務
public class MonitorSocketServer {
ServerSocket s = new ServerSocket(PORT);
try{
while(true){
Socket socket = s.accept();
try{
new ServerOneJabber(socket);
} catch(IOException e){
socket.close();
}
}
以上是服務端Socket的創(chuàng)建過程,它主要是負責對指定的端口進行監(jiān)聽,如果有請求進來則響應,調用相應的處理類。
4.2客戶端程序
這是Socket客戶端的創(chuàng)建過程,它主要是與Socket服務端進行通訊。我們的應用是要根據Oracle數據庫中數據變化而進行實時的觸發(fā)通訊,因此在用戶修改了數據提交數據庫后,由數據庫觸發(fā)器直接調用Socket通知遠程服務端啟動。Oracle的觸發(fā)器是不能訪問Java代碼的,只能通過存儲過程來訪問,而存儲過程要使用Java代碼只能通過Oracle提供的工具LoadJava將客戶端MonitorSocketClient裝載到Oracle數據庫中,作為一個數據對象供Oracle調用:
LoadJava-u scott/tiger-r-v-f c:/MonitorSocketClient.java
4.3存儲過程調用客戶端程序
CREATE OR REPLACE PROCEDURE Call_Monitor(ip varchar2,port varchar2,message varchar2)
as language java
name ' MonitorSocketClient.main(java.lang.string[])';
接下來,Socket服務端需要根據傳入的參數對客戶端進行控制:由于Java沒有指針等概念,無法獲得我們的應用程序進程號,因此采用JNI編程,調用C++函數來獲取程序進程,操作應用程序進行相應的處理:
public class ControlClientApplication{
public native long getProcessInfo(String);
public native int dealRemoteControl(String applicationName , );
public static void main (String[ ] args){
System.loadLibray(“ControlObject”);
ControlObject CObject = new ControlObject;
long ProcessId = getProcessInfo(args[ 0]);
編譯后使用javah將ControlClientApplication.class文件編譯成為一個C++的頭文件(ControlClientApplication.h)。這個工具被設計成用來創(chuàng)建頭文件,該頭文件為在java 源代碼文件中所找到的每個native方法定義 C 風格的函數。我們用C++編寫一個實現:
#include “ControlClientApplication.h”
#include
#include
HANDLE hProcessSnapShot = NULL;
PROCESSENTRY32 pe32 = {0};
int iLen;
CString strSpace = “”;
hProcessSnapShot = (HANDLE)CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
pe32.dwSize = sizeof(PROCESSENTRY32);
Process32First(hProcessSnapShot,&pe32);
do {
strSpace = “”;
strAppName = pe32.szExeFile;
for(iLen = 0; iLen<60-strAppName.GetLength(); iLen++)
strSpace = strSpace + “”;
strAppName = strAppName + strSpace;
} while(Process32Next(hProcessSnapShot,&pe32));
CloseHandle(hProcessSnapShot);
在Powerbuilder的主界面窗口中加入刷新事件(pbvm_paint)的處理方法:
Choose Case Messae.LongParm
Case 0
Case 1
Open(w_netwatch)
End Choose
5、結論
以上方法作者已在開發(fā)的財政橫向網信息系統(tǒng)調用財政集中支付系統(tǒng)中應用,證明是有效的。但由于采用了Windows的消息機制、動態(tài)鏈接庫,因此整個解決方案必須基于Windows平臺,跨平臺性能不佳。
參考文獻
1、Tom Portfolio.Java Stored Procedures Developers Guide.Oracle Corporation.1999
2、Bruce Eckel,Thinking in Java, president, MindView, Inc.2002
3、劉丹華,黃道君.利用套接字開發(fā)網絡通信程序.微機發(fā)展.2003年1月
(作者單位:湖南省財政廳)