這是本專題的續集,沒讀過第一部的看這里:
http://bbs.2ccc.com/topic.asp?topicid=548153
之所以要搞第二部是因為第一部跟貼太多,讀起來不方便,浪費大家的時間。
今天咱們聊的主題是:Delphi的DataSnap實質分析
先說DataSnap中文應該翻譯成什么,我個人的譯法是:數據快照。DataSnap是后來名字,原來叫MIDAS, Multi-tier Distributed Application Services Suite( 多 層 分 布 式 應 用 程 序 服 務 包) 的 縮 寫。大家不要被這么多介紹DataSnap的資料弄暈了,其實原理非常簡單。
要把DataSnap搞明白,必須先把客戶端的TClientDataset控件搞明白,不會,找度娘。下面簡稱CDS。
CDS有兩個OleVarient屬性,一個叫Data,一個叫Delta。Delphi的多層框架全靠這哥倆。Data用於客戶端從服務器端獲取數據,Delta用於客戶端將修改的數據保存到服務器端。
那么,這就簡單了,服務器端只要能實現輸出Data,接收Delta,三層應用就搞起來了。服務器端這項工作安排給誰呢?TDatasetProvider,下面簡稱DP。
DP有一個Data屬性,也是OleVariant類型,還有一個ApplyUpdate方法,接受Delta作為輸入參數。
明白了這個道理,我們完全可以拋開Delphi那個復雜的DataSnap不理,自己來構建簡單可靠而且高效的多層框架。
服務器端自然用mORMot來稿,用THttpApiServer+DP,充分發揮http.sys的威力,站在巨人的肩膀上,呼風喚雨。
客戶端我們用Delphi10.2.3來做,支持PC/Android/iOS, 用CDS+TNetHTTPRequest來做。TNetHTTPRequest為XE8新增控件,使用操作系統內置http與https,不需要indy與openssl。追求極致的還可以用更底層的THTTPClient控件。
不考慮手機的,繼續用Delphi7做PC應用的鐵粉,用CDS+THttpClientSocket(mORMot自帶)。
Data與Delta都是Variant,無法在網絡上傳輸,我們需要這倆變成字符串,先來兩個函數:
{$IFNDEF UNICODE}
type
RawByteString = AnsiString;
{$ENDIF}
function VariantArrayToString(const V: OleVariant): RawByteString;
var
P: Pointer;
Size: Integer;
begin
Result := '';
if VarIsArray(V) and (VarType(V) and varTypeMask = varByte) then begin
Size := VarArrayHighBound(V, 1) - VarArrayLowBound(V, 1) + 1;
if Size > 0 then begin
SetLength(Result, Size);
P := VarArrayLock(V);
try
Move(P^, Result[1], Size);
finally
VarArrayUnlock(V);
end;
end;
end;
end;
function StringToVariantArray(const S: RawByteString): OleVariant;
var
P: Pointer;
begin
Result := NULL;
if Length(S) > 0 then begin
Result := VarArrayCreate([0, Length(S) - 1], varByte);
P := VarArrayLock(Result);
try
Move(S[1], P^, Length(S));
finally
VarArrayUnlock(Result);
end;
end;
end;
看明白了嗎?Data與Delta內部存儲的都是字節流。變成字節流以后有兩種傳送方式,一種是以Stream方式,ContentType設置成application/octet-stream;另一種將字節流base64編碼成純文本。大數據流可以加入壓縮/減壓機制,保密數據可以加入加密/解密機制。
base64編碼與解碼,Delphi自帶,單元名為EncdDecd。里面有EncodeString與DecodeString兩個函數。
為了讓大家把原理搞懂,我們先拋開網絡傳輸層,將CDS與DP放在一個屋子里讓他倆親熱一把。
unit DP2CDSMain;
interface
uses
Forms, DBClient, DB, Provider, ADODB, Controls, Grids, DBGrids, ComCtrls,
Classes, StdCtrls;
type
TForm2 = class(TForm)
ServerData: TADODataSet;
Button1: TButton;
ClientData: TClientDataSet;
ServerDataSetProvider: TDataSetProvider;
PageControl1: TPageControl;
TabSheet2: TTabSheet;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
uses SysUtils, Variants, EncdDecd;
{$R *.dfm}
{$IFNDEF UNICODE}
type
RawByteString = AnsiString;
{$ENDIF}
function VariantArrayToString(const V: OleVariant): RawByteString;
var
P: Pointer;
Size: Integer;
begin
Result := '';
if VarIsArray(V) and (VarType(V) and varTypeMask = varByte) then begin
Size := VarArrayHighBound(V, 1) - VarArrayLowBound(V, 1) + 1;
if Size > 0 then begin
SetLength(Result, Size);
P := VarArrayLock(V);
try
Move(P^, Result[1], Size);
finally
VarArrayUnlock(V);
end;
end;
end;
end;
function StringToVariantArray(const S: RawByteString): OleVariant;
var
P: Pointer;
begin
Result := NULL;
if Length(S) > 0 then begin
Result := VarArrayCreate([0, Length(S) - 1], varByte);
P := VarArrayLock(Result);
try
Move(S[1], P^, Length(S));
finally
VarArrayUnlock(Result);
end;
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
vDataIn, vDataOut: OleVariant;
cDataIn, cDataOut: RawByteString;
begin
ClientData.Close;
vDataIn := ServerDataSetProvider.Data;
cDataIn := VariantArrayToString(vDataIn);
//模擬網絡傳送
cDataOut:=cDataIn;
vDataOut := StringToVariantArray(cDataOut);
ClientData.Data := vDataOut;
end;
end.
我這里轉載了一篇文章細說CDS的用法:
https://www.cnblogs.com/c5soft/p/9121775.html
沒仔細看過手機版的CDS,不知道是不是完全實現了PC版的功能,用過的朋友多發帖。
說點題外話,CDS鼠標右鍵菜單有一項“Assign Local Data...”,可以將相同窗體上的任何TDataset的數據復制到CDS中,如何實現的呢?我猜想就是用到了DP, 應該是這樣寫的:
var DP:TDatasetProvider;
begin
DP:=TDatasetProvider.Create;
DP.Dataset=ADODataset1;
ClientDataset1.Data:=DP.Data;
DP.Free
end;