而今網際網路盛行的 TCP/IP 協定,能將網路效能發揮到最大極限,但,無奈傳統的 DBF 存取卻不支援!
但,在一群 Harbour 的開發者努力之下,卻也看到了希望!
想透過 TCP/IP 協定存取 DBF,不只是單單的檔案存取而已,還能做到 C/S 架構的存取,神奇吧!
我們可以單獨寫一支主程式在主機上跑,專門處理工作端送來的命令,
這樣子有什麼好處呢?試想,如果工作站有 Win7/Vista/XP/2000/98/DOS ...
自己透過檔案鎖住機制去存取 DBF/CDX, 不同 OS 有不同機制,
索引檔常常損壞,往往是這種情況下造成的,而且,DBF 的檔案鎖住機制
也非完美,所以才會常常發生 DBF/CDX 資料錯亂問題發生。
如果我們可以改用 C/S 架構跑我們的管理系統,有一支專門處理工作站的資料工作,
那對於 DBF/CDX 的穩定性,就會大大提高了!
但是,也非完美無缺的,至少在我的開發環境 Harbour + FWH 之下,
測試透過網際網路進來存取,也許是 TcBrowse 寫的不好,三不五時就要更新畫面資料,
導致這樣子的連線方式,畫面卡卡的,至於純 Console 下能否跑得順暢?
我沒這樣子的案子可以測試.
另外,使用 xBrowse 能否改善?因為在下也沒在用 xBrowse ,所以這方面也無法提供測試結果.
若是單純在內部網路(100MB網卡)下跑,效能不錯喔!
話說回來,想要讓你的 DBF 跑 C/S 架構,是有兩項產品可以作看看,
而在下我,目前只有測試 Harbour 的 hbnetio, 另外一套也是免費的 letoDB,
在下沒測試過,不知道情況好壞,不予置評... (未完,待續...)
說到這 hbnetio 玩意,是打從 harbour 2.0 開始才有的東西,比起 LetoDB 要來得晚些,
但,好歹也是 Harbour 核心開發者搞出來的玩意,穩定性上來說,也要來的強!
在下目前已經利用此功能,開發出兩套依附此項功能開發而成的軟體,
客戶反應上來說,已經很少出現索引檔壞掉問題了。
要介紹 hbnetio 時,得先介紹主機上執行的主程式,由於在下使用的是 nightly CVS Harbour(就是每天晚上會自動產生的新版本啦),
所以,在程式目錄下: %hbdir%\bin\ 下就有一支 hbnetio.exe,但是,不建議直接使用這支程式來執行,
因為少了很多函數支援功能,建議還是依照自己需求,重新編譯一支來用.
就來說說我個人開發這兩套系統所遇到的問題一一來解說,主機端的 hbnetio.exe 需具備哪些功能?
哪些功能是辦不到?哪些需要特別處理的?且聽我一一道來...
基本上,想要擁有完整的 harbour 函數功能支援的話,首先,必須必須先修改 netiosrv.prg(檔案位於 %hbdir%\contrib\hbnetio\utils\裡面),
找到:
代碼: 選擇全部
#ifdef HB_EXTERN
:
#endif
代碼: 選擇全部
// #ifdef HB_EXTERN
:
// #endif
hbnetio 還支援一些參數,在下沒仔細研究,有興趣的自行研究吧!
代碼: 選擇全部
c:\>hbnetio.exe -rpc
主機端其他一些注意事項,後面再詳述,先來說說 client 端程式如何寫!!
Client 端也很簡單,參考如下程式碼:
代碼: 選擇全部
func main()
if .not. hbnetio_connect( '192.168.1.100', 2941)
alert('Can not connect to server!!')
quit
else
alert('Connect success!!')
endif
要使用 hbnetio, 於連結時,需加上一些 library,
因為在下使用 bcc55, 所以需要連結:
代碼: 選擇全部
\hb21\lib\hbzlib.lib +
\hb21\lib\hbnetio.lib +
\bcc55\lib\psdk\ws2_32.lib +
代碼: 選擇全部
cPath := netio_funcexec( 'hb_argv', 0 ) // 取得主機端 hbnetio.exe 執行檔路徑
alert(cPath)
那就代表你的主機程式 hbnetio.exe 執行時忘了加上參數 '-rpc' 囉!
未完,待續....
說說 hbnetio Client 端支援的函數功能吧!
不知道這些功能,還真不知道該怎麼執行遠端函數、命令功能哩!
上面範例提到的: NETIO_FUNCEXEC( <cFuncName> [, <params,...>] ) -> <xFuncRetVal>
我們可以直接執行主機上的函數功能,例如上例:
cPath := netio_funcExec( 'hb_argv', 0 )
他的意思就等同於在主機端執行了 hb_argv(0) 的意思!
並將結果回傳至變數 cPath 去,所以,我們可以藉此得到主機端
執行 hbnetio.exe 時的所在路徑與檔案名稱.
另外,還提供了:
NETIO_PROCEXISTS( <cProcName> ) -> <lExists>
讓你可以在執行函數/命令/程序之前,先判斷主機端是否具備執行的能力,
才不會造成執行過程中,產生錯誤訊息回應.
雖然名稱稱之為 "ProcExists",但是,是支援 Function/Procedure/Command.
NETIO_PROCEXEC( <cProcName> [, <params,...>] ) -> <lSent>
NETIO_PROCEXECW( <cProcName> [, <params,...>] ) -> <lExecuted>
這兩個函數的差異向在於是否等待主機端回應.
hbnetio 支援所有的 RDD 功能,所以,只要是 RDD 的操作函數與命令,
都可以執行,例如: dbSkip()/dbGoTo()/dbSeek()/Eof()/Bof().... 等等.
這些功能測試,可以直接參考 %hbdir%\contrib\hbnetio\tests\ 裡面的 .prg,
這邊就不再累贅敘述了.
再來談談如何將一般的 file server 架構,修改為 C/S 架構的程式?
雖然稱之為 C/S 架構,但是,也只是半套的 C/S 罷了!怎麼說呢?
正常的 C/S 架構,Server Side 是要具備「運算」功能的,而 hbnetio 卻只提供
RDD 方面的操作功能而已,說穿了,是幫你作 Lock 動作,資料編輯,操作,Unlock...
一般我們所知的 C/S 架構中的 Server Side, 可是還要能作複雜加減與搜索運算,
所以我才會稱之為半套的 C/S 架構!
修改之前,先確認你想用哪種方式來修改?一般來說有兩種方式:
1. 直接將程式全部修改為 netio 方式,不再支援 file server 舊架構,這種方式,
那就全部將要在主機端運作的函數全面性的直接加入 netio 資料.
2. 將程式修改為同時支援 file server 與 c/s 架構,這樣子的方式,就得作一些判斷上的修改了,
在下是採用這項,因為有套程式,不是每個人都要更換,所以就採用第二種方式來處理,以下的說明都是針對這第二種方式.
這種方式也有個好處,當我要啟動 netio 功能時,我只要設定一個變數 '_NETIO'
包含了主機 ip/port/路徑等資料,如果不開啟時,直接將 '_NETIO' 設為空字串,
那麼,一些函數在執行上,我可以直接採用例如: dbExist( _NETIO+'abc.dbf' )
啟動 netio 時,先組合 '_NETIO' 變數為: 'net:192.168.1.1:/',
所以上面的函數就自動變成了 dbExist( 'net:192.168.1.1:/'+'abc.dbf' )
要關閉 netio 功能時,只要將變數 '_NETIO' 設為空字串,那上面函數就自動變成
dbExist( ''+'abc.dbf' ) 了,如此方便,也可以提供客戶在整個程式尚未修改完成之前
繼續使用舊的架構執行!
修改時的注意事項:
1. 檢查 dbf 檔案是否存在?
在以前,我們可能習慣用 File() 來檢查,在此要修改為 dbExists(),
2. 修改 dbPack() 為 hb_dbPack(),
dbZap() 為 hb_dbZap(),
另外: dbSkipper() 是 Harbour 從 2.0 到 2.1 的變化,原本的定義要修改為
代碼: 選擇全部
#xtranslate _DbSkipper => __dbSkipper // Harbour 2.1.x
代碼: 選擇全部
#xtranslate _DbSkipper => DbSkipper
lIsDir() / lMkDir() 這兩個函數,一個是判斷目錄是否存在,一個用來建立目錄,
我們需要作「適當」的調整如下:
代碼: 選擇全部
If Empty( _NETIO )
// 一般網路協定
If .Not. lIsDir( _cStkDir )
If .Not. lMkDir( _cStkDir )
Tone( 1000, 1 )
MsgStop( "資料檔案目錄 "+_cStkDir+" 無法建立,請檢查原?]!", "警告!" )
Exit
EndIf
EndIf
Else
// NETIO 網路
If ! netio_funcexec( 'hb_dirExists', _cStkDir )
If netio_funcexec( 'MakeDir', _cStkDir ) <> 0
Tone( 1000, 1 )
MsgStop( "資料檔案目錄 "+_cStkDir+" 無法建立,請檢查原?]!", "警告!" )
Exit
EndIf
EndIf
EndIf
_cStkDir += "\"
這個方法就是我上面說的,可以讓程式同時支援舊的 file server 與新的 c/s 架構同時存在方法.
4. Directory() 函數也要調整,如果是要檢查本地端的目錄,就不用修改,如果要檢查遠端目錄,
那就要修改為: lExist := hbnetio_funcExec( 'Directory', '*.*', 'D' ) 來判斷目錄是否存在了.
5. 再來針對主機端上的程式再加強功能,低階檔案的修改。
hbnetio 目前不支援遠端低階檔案功能,其實問題在於 fRead() 這個函數,只要自己重寫一個替代函數就可以了!!
針對此項問題,在下於主機端程式加上了幾個函數來替代:
(請修改 %hbdir\contrib\hbnetio\utils\netiosrv.prg 於尾端加上)
代碼: 選擇全部
// 為了相容 fRead(), 所以 cBuffer 保留
FUNC ssfRead(nH, cBuffer, nReadBytes)
Local cH := PadL(nH,5,'0')
afBuffer[cH] := Space(nReadBytes)
RETURN fRead( nH, @afBuffer[cH], nReadBytes)
// 取回 Buffer 資料
FUNC ssfBuffer(nH)
RETURN afBuffer[PadL(nH,5,'0')]
// 關閉檔案
FUNC ssfClose(nH)
Local cH := PadL( nH, 5, '0' )
Local nRet := fClose( nH )
// 清除 afBuffer[cH] 所佔的記憶體
If hb_HHasKey( afBuffer, cH )
If afBuffer[cH] <> ''
afBuffer[cH] := ''
EndIf
afBuffer[cH] := NIL // 將 Hash() 所佔空間?]為 NIL
hb_HDel( afBuffer, cH ) // 刪除 hash() 所佔空間
EndIf
hb_gcall()
RETURN nRet
代碼: 選擇全部
// add:WenSheng
// 為了改變 fRead() 問題
STATIC afBuffer // , afHandle
// end:WenSheng
PROCEDURE Main( ... )
LOCAL netiosrv[ _NETIOSRV_MAX_ ]
LOCAL cParam
LOCAL cCommand
LOCAL cPassword
:
:
代碼: 選擇全部
HB_Logo()
// add:WenSheng
afBuffer := hb_Hash()
// end:WenSheng
而最大問題在於 fRead(), 因為此函數同時回傳兩個資料,而 hbnetio 一次只能接收一個參數,
所以在下重寫了一個 ssfRead() 來替代原有的 fRead() 函數,先傳回此函數讀取到的 Bytes 數,
並將資料存入變數之中,然後再利用在下另外新增的 ssfBuffer() 來取回剛剛的資料.
如此一來,就可以解決 fRead() Buffer 問題了!
因為採用了 hash() 來寫,所以,fClose() 也被改寫了,用來釋放 buffer 變數記憶體,
當這個低階檔案問題解決之後,接下來就可以利用這個低階檔案功能幹一些勾當了!
例如:在下有個 fCopy() 函數功能,用來複製檔案,並且加了複製進度表顯示,
fCopy( cSource, cTarget ), 我就可以修改此函數,想要複製遠端檔案到本地端來,
我就可以寫成: fCopy( _NETIO+cSource, cTarget ),
如果想將本地端檔案複製到遠端主機去,
我也可以寫成: fCopy( cSource, _NETIO+cTarget ),
我只要修改 fCopy() 這個函數,判斷 cSource/cTarget 哪個變數含有 _NETIO 字串,
有此字串的,代表這個是遠端,沒有的是本地端,範例程式如下(包含了 FWH code):
代碼: 選擇全部
Func fCopy()
Para cSource, cTarget
Priv nRetByte := 00,; && RETURN COPY BYTE
nReadByte := 00,;
nWriteByte := 00,;
nTolByte := 00,;
nSHandle := 00,; && SOURCE FILE HANDLE
nTHandle := 00,; && TARGET FILE HANDLE
nBuffer := 00,;
cBuffer := '',;
nNowCopyByte := 00,;
nTolCopyByte := 00,;
xUN ,;
lSource := .F.,;
lTarget := .F.
*
If ValType( cSource ) # 'C' .Or. ValType( cTarget ) # 'C'
Return nRetByte
EndIf
//
If Empty(_NETIO)
// 一般網路,不用管哪個為遠端或近端
lSource := .F.
lTarget := .F.
Else
// NETIO: 檢查哪一端是 NETIO
If _NETIO $ cSource
lSource := .T.
cSource := Substr( cSource, Len(_NETIO)+1 ) // 去除 _NETIO
EndIf
If _NETIO $ cTarget
lTarget := .T.
cTarget := Substr( cTarget, Len(_NETIO)+1 ) // 去除 _NETIO
EndIf
EndIf
//
MsgMeter( {|oM,oT,oD,lEnd|_FCopy(oM,oT,oD,@lEnd)}, "資料拷貝中....", cSource+" 檔案拷貝中", .F. )
*
Return nRetByte
* 進度表
Func _FCopy(oM,oT,oD,lEnd)
*
Do While .T.
// ?}啟原始檔案
If Empty(_NETIO) .Or. ! lSource
// 一般網路
nSHandle := fOpen( cSource, 66 )
Else
// NETIO 網路
nSHandle := netio_funcexec( 'fOpen', cSource, 66 )
EndIf
If nSHandle == -1 && SOURCE ?}檔錯誤
Exit
EndIf
// 計算原始檔案大小
If Empty(_NETIO) .Or. ! lSource
// 一般網路
oM:nTotal := nTolCopyByte := fSeek( nSHandle, 0, 2 ) && 移至檔尾,取得TOLBYTE
fSeek( nSHandle, 0 ) && 移回檔頭
Else
// NETIO 網路
oM:nTotal := nTolCopyByte := netio_funcexec( 'fSeek', nSHandle, 0, 2 ) && 移至檔尾,取得TOLBYTE
netio_funcexec( 'fSeek', nSHandle, 0 ) && 移回檔頭
EndIf
// 建立目的檔
If Empty(_NETIO) .Or. ! lTarget
// 一般網路
nTHandle := fCreate( cTarget )
Else
// NETIO 網路
nTHandle := netio_funcexec( 'fCreate', cTarget )
EndIf
If nTHandle == -1 && TARGET ?}檔錯誤
Exit
EndIf
// ?]定緩衝區大小
nBuffer := 30 * 1024
cBuffer := Space( nBuffer )
// 讀取 Source 端資料
If Empty(_NETIO) .Or. ! lSource
// 一般網路
nReadByte := fRead( nSHandle, @cBuffer, nBuffer )
Else
// NETIO 網路
nReadByte := netio_funcexec( 'ssfRead', nSHandle, @cBuffer, nBuffer )
EndIf
If nReadByte == 0
Exit // 讀不到東西,離?}
EndIf
If Empty(_NETIO) .Or. ! lSource
// 一般網路
Else
// NETIO 網路
cBuffer := netio_funcexec( 'ssfBuffer', nSHandle ) // 取回 Buffer 資料
EndIf
//
Do While .T.
//
oM:Set( nTolByte )
SysRefresh()// oD:UpDate()
// 將資料寫入目的檔
If Empty(_NETIO) .Or. ! lTarget
// 一般網路
nWriteByte := fWrite( nTHandle, cBuffer, nReadByte )
Else
// 一般網路
nWriteByte := netio_funcexec( 'fWrite', nTHandle, cBuffer, nReadByte )
EndIf
If nWriteByte <> nReadByte
Exit // 寫入錯誤
Else
nTolByte += nWriteByte // 已經寫入多少 Bytes 資料
EndIf
// 讀取原始檔資料
If Empty(_NETIO) .Or. ! lSource
// 一般網路
nReadByte := fRead( nSHandle, @cBuffer, nBuffer )
Else
// NETIO 網路
nReadByte := netio_funcexec( 'ssfRead', nSHandle, @cBuffer, nBuffer )
EndIf
If nReadByte == 0
Exit
EndIf
If Empty(_NETIO) .Or. ! lSource
// 一般網路
Else
// NETIO 網路
cBuffer := netio_funcexec( 'ssfBuffer', nSHandle ) // 取回 Buffer 資料
EndIf
//
EndDo
//
oM:Set( nTolCopyByte )
oD:UpDate()
//
nRetByte := nTolByte
Exit
//
EndDo
//
If nTHandle <> -1
If Empty(_NETIO) .Or. ! lTarget
// 一般網路
fClose( nTHandle )
Else
// NETIO 網路
netio_funcexec( 'ssfClose', nTHandle )
EndIf
EndIf
If nSHandle <> -1
If Empty(_NETIO) .Or. ! lSource
// 一般網路
fClose( nSHandle )
Else
// NETIO 網路
netio_funcexec( 'ssfClose', nSHandle )
EndIf
EndIf
//
lEnd := .T.
oD:End()
//
Return NIL
未完,待續....
6. 針對 .ini 讀寫問題作修改:
因為在下用了 FWH, 所以,直接用 FWH 提供的 GetPvProfString/WritePProString
就算你直接用 harbour 的 ini 功能,還是得面對遠端檔案讀寫問題!
在下的解決方法,是利用轉換命令方式來處理,為每一支程式碼加上一個函數檔:
代碼: 選擇全部
#include "xxx.ch"
代碼: 選擇全部
#xtranslate GetPvProfString([<x, ...>]) => ssWinIni( 'GetPvProfString', <x>)
#xtranslate WritePProString([<x, ...>]) => ssWinIni( 'WritePProString', <x>)
是要從遠端存取?還是本地端存取?
代碼: 選擇全部
Func ssWinIni( cFuncName, x1, x2, x3, x4 )
Local cPath := x4 // 檔案名稱+路徑
Local nAt := At( _NETIO, cPath) // 第五個為 ini 檔案名稱+路徑(第一個函數名稱)
Local cFile := ''
If ! Empty( _NETIO ) .And. nAt > 0
cFile := Substr( cPath, Len(_NETIO)+1 ) // 去掉 _NETIO 字串,後面的資料才是我們要的「主機路徑」
Return netio_funcexec( cFuncName, x1, x2, x3, cFile )
Else
Return &cFuncName(x1, x2, x3, x4)
EndIf
如果不包含 _NETIO 字串,就代表這個檔案是本地端檔案!就這麼簡單,不是嗎?Very Happy
如果你要存取遠端 .ini 中的資料,那就可以寫成:
代碼: 選擇全部
WritePProString( 'SYSVAR', 'IP', "127.0.0.1", _NETIO+'\abc.ini' )
代碼: 選擇全部
WritePProString( 'SYSVAR', 'IP', "127.0.0.1", '\abc.ini' )
7. 再來玩一個好玩的,在遠端執行壓縮程式備份功能,
代碼: 選擇全部
cCmpStr := 'WinRAR.EXE -a -y test.rar *.dbf'
netio_funcexec( 'hb_run', cCmpStr )
fCopy() 函數功能,就可以將遠端的 .DBF 給壓縮、複製到本地端了!
附上我所修改的 netiosrv.prg
netiosrv.prg
記得加上 FWH 的 profile.c, 否則無法存取 .ini
另外再附上已經編譯好的 hbnetio.exe ,可以直接執行 hbnetio.exe -rpc 就好了.
ps. 不放心的人請重新編譯吧!!
hbnetio.exe
[完]... 有想到再補上來!若是你有任何好的建議也可以提供出來大家一起研究!
[End]