这是本专题的续集,没读过第一部的看这里: 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;
|
请发表评论