使用http.sys,让delphi的多层服务真的飞起来【第二部】

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

 

posted @ 2018-06-01 22:57  c5soft  阅读(1550)  评论(0编辑  收藏  举报