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