DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: jit6266668
今日帖子: 2
在线用户: 4
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/13 12:18:28
标题:
斗胆开个Datasnap交流贴 浏览:2870
加入我的收藏
楼主: 首先声明,开此贴仅并不是想说明Datasnap的好与不好,也不包含其它任何比较之意,仅是靠个人对Delphi的喜爱而开此贴。

限于各种原因,本人并没有什么Datasnap的实际经验,只是业余时间喜欢“把玩”--写些小DEMO测试,所以很抱歉,无法提供Datasnap的一些性能测试的数据,仅能提供少许写DEMO时的体会,即希望能抛砖引玉,也希望对刚接触Datasnap的新人有所帮助。若有不正确之处,欢迎指出。

如果“脏”了谁的眼,勿喷,请绕行。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/13 12:21:52
1楼: 鄙人将整理一些之前的DEMO,找时间上来补充----虽然有些可能在网上能搜到。
----------------------------------------------
学无止境
作者:
男 kingofsun (小矮人酋长) ▲▲▲▲▲ -
普通会员
2015/1/13 12:30:28
2楼: 呵呵顶一个,
----------------------------------------------
-
作者:
男 topok (topok) ★☆☆☆☆ -
盒子活跃会员
2015/1/13 12:51:04
3楼: http://blog.csdn.net/shuaihj/article/details/6129131
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/13 13:23:12
4楼: 在写代码之前,首先要考虑使用的协议类型, 是tcp/ip还是http(s),不同的协议对应不同的设计,tcp/ip是长连,服务器端可以知道当前有多少在线用户,http可以方便的改为IIS方式,不同的协议类型,Server Class LifeCycle(Server Class是指TDSServerClass)可以有不同的选择,不同的选择对应不同的设计,
Server Class LifeCycle对于Datasnap的性能是非常重要的,分为
Server, Session, Invocation三种,每种类型的特点,网上可搜到相关文章或李维先生的电子书。

对于服务程序的性能,除硬件外就是靠软件方面的优化了,Datasnap内部使用的是indy组件,是阻塞的,每次响应都依赖一个线程,datasnap本身提供了“池”的功能。所以服务方法要尽快的完成,好让线程回到池中再利用。在并发和负载较大的程序中是很重要的。所以不管是tcp或http,尽量分批取少量的数据,如果的确要取大量的数据,不如直接开个dbxcallback(回调)更好,还可以在客户端显示进度。
----------------------------------------------
学无止境
作者:
女 judger (ERP) ★☆☆☆☆ -
盒子活跃会员
2015/1/13 13:29:43
5楼: 为了回复你,我登录了一下系统,支持!!!
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/13 13:39:15
5楼: 除了分批次,少量取数据提高性能外,服务方法中参数的数据类型也会对性能有影响,Datasnap是基于DBX framework的,在delphi帮助提到
Currently, using a TDBXValue is the fastest way to pass a parameter, because these are the internal objects used to manage parameter lists.
如果参数是一般的string等简单类型,基本可以忽略,但如果参数是TStream类型时,可以用TDBXStreamValue来替换,记得在XE2时候测试过,比一般TStream类型节约30%的返回时间。
----------------------------------------------
学无止境
作者:
男 smartdata (smartdata) ▲▲▲▲▲ -
普通会员
2015/1/13 13:40:50
4楼: 好帖,顶楼主!
----------------------------------------------
==========
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/13 13:56:59
6楼: 感谢judger (ERP)的支持.

继续
function CreateDBXStreamValue: TDBXStreamValue;
var
  VT: TDBXValueType;
begin
  VT := TDBXValueType.Create(nil);
  VT.DataType := TDBXDataTypes.BlobType;
  VT.ValueTypeFlags := TDBXValueTypeFlags.ExtendedType;
  Result := TDBXStreamValue.Create(VT);
end;
创建一个TDBXStreamValue可用上面的代码,
  VT.ValueTypeFlags := TDBXValueTypeFlags.ExtendedType;这行代码在XE2中,好像是需要的,在XE?好像不再需要了(或许也需要)记不清了(当时是写DEMO跟踪发现的,心血啊)。
      procedure SetStream(const Stream: TStream; const AInstanceOwner: Boolean); override;
      function  GetStream(AInstanceOwner: Boolean): TStream; override;
这两个方法对应服务端和客户端的取值,其中的AInstanceOwner参数比较重要,可能会造成内存泄漏或报异常,客户端需要和下面TServerMethodsClient的Create方法搭配使用。
    TServerMethodsClient.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
实际上,TServerMethodsClient.Create中的AInstanceOwner是比较重要的,在传一些对象参数时,需要注意。后面我会写一些经验,但还是要靠个人摸索掌握。
----------------------------------------------
学无止境
作者:
男 looper (keyo) ★☆☆☆☆ -
盒子活跃会员
2015/1/13 14:06:36
7楼: 难得有这种好帖子。支持一下。
----------------------------------------------
虽千万人吾往矣!
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/13 14:20:32
7楼: 除了参数类型外,通过Server Class LifeCycle的Server, Session, Invocation三种类型,还可以"榨出"Datasnap一些性能来,这个技巧应该在XE or XE2时就有了,当时EMB在土豆,奇艺上发过一些视频,基中就有介绍,目前在EMB的网站上也还有。具体的代码就不上了,主要是说说原理。
当使用tcp协议时,TDSServerClass的LifeCycle可以设置为Server,Session, Invocation的一种,http时,delphi的文档上说:
For a REST client connection, if Session LifeCycle is used on the server class, it behaves like Invocation LifeCycle. 
所以利用Invocation类型,我们可以缓存TDSServerClass实例。具体办法是
在TDSServerClass的OnCreateInstance和OnDestroyInstance事件中池化TDSServerClass的实例,这和网上写的监控客户端连接是一个意思。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/13 14:46:47
8楼: 除此之外,涉及到数据库的,自觉使用连接池(自己实现也好,用fdac的也好)
一般的数据查询,使用DBX framework时,返回DBXReader最好了。(目前未与直接返回TDataset对比过,感觉dataset最终也会转为dbxreader)

慎用TDSClientCallbackChannelManager的注册回调,除非只是为了接收其它客户端或服务器端的消息。 如果有SQLConnection产生一个连接了, 不要用TDSClientCallbackChannelManager的BroadcastToChannel或NotifyCallback方法,而要通过SQLConnection调用服务器的一个方法进行广播或通知其它客户端----原因后续再说明。
----------------------------------------------
学无止境
作者:
男 zhuzh_yuy (华) ▲▲▲▲▲ -
普通会员
2015/1/13 18:14:46
9楼: 好贴,严重支持
----------------------------------------------
-
作者:
男 yemny (yemny) ★☆☆☆☆ -
盒子活跃会员
2015/1/13 18:59:59
10楼: 鼓掌 顶 支持 威武
比口水 睡好 睡不好 个人感觉 好 5倍
----------------------------------------------
-
作者:
男 yagzh (心不了情) ▲▲▲▲▲ -
盒子活跃会员
2015/1/13 19:26:58
11楼: 顶一下
----------------------------------------------
-
作者:
男 wangdejun (wangdejun) ▲▲▲▲▲ -
普通会员
2015/1/14 8:00:03
12楼: 对初学者有意义的,支持
----------------------------------------------
-
作者:
男 shloverxp (shloverxp) ★☆☆☆☆ -
盒子活跃会员
2015/1/14 9:41:17
13楼: 将帖子标注为好帖,将LZ标注为高手
----------------------------------------------
-
作者:
男 fky1989 (fky1989) ▲▲▲△△ -
注册会员
2015/1/14 9:44:10
14楼: 好贴 支持lZ
----------------------------------------------
-
作者:
男 looper (keyo) ★☆☆☆☆ -
盒子活跃会员
2015/1/15 14:08:36
15楼: 顶。
----------------------------------------------
虽千万人吾往矣!
作者:
男 pengmg (Challey) ▲▲▲▲▲ -
普通会员
2015/1/15 19:23:37
16楼: 是的,请有过实际案例的把情况发上来,讨论讨论
----------------------------------------------
-
作者:
男 hzforce ( ) ★☆☆☆☆ -
普通会员
2015/1/18 10:37:52
17楼: 好贴! 另外请教一个问题:

datasnap服务器上的方法,如果用http方式请求的话,他会返回 
{"result":[{"name":"123","dfd":1}]}
这种格式,能有办法让他直接返回
{"name":"123","dfd":1}
这样的格式吗?
----------------------------------------------
-
作者:
男 bigboy2050 (bigboy2050) ▲▲▲▲▲ -
注册会员
2015/1/18 11:09:15
18楼: 好!!!
----------------------------------------------
http://www.kittyapp.net
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/18 14:19:16
19楼: to: hzforce ( )

//----------

procedure TWebModule1.DSHTTPWebDispatcher1FormatResult(Sender: TObject;
  var ResultVal: TJSONValue; const Command: TDBXCommand; var Handled: Boolean);
begin
  Form1.Memo1.Lines.Add(command.Text);
  Form1.Memo1.Lines.Add(ResultVal.ToString);
  if Command.Text='TServerMethods1.ReverseString' then
    Handled := True
  else
    Handled := False;
end;

//----------
IE:
http://localhost:8080/datasnap/rest/TServerMethods1/ReverseString/abc

//----------
测试结果:  

["cba"]
----------------------------------------------
学无止境
作者:
男 dawnhawk (dawnhawk) ★☆☆☆☆ -
盒子活跃会员
2015/1/18 15:36:55
20楼: 每天来看看楼主有没有更新,谢谢!
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/18 19:59:58
21楼: 谢谢dawnhawk (dawnhawk)和其它朋友的关注,这几天有点事情耽搁了。
总体来说,datasnap用起来是比较简单的,从精灵向导生成以及相关的组件属性,事件,方法上也可以看出来,所以使用中可控的东西不是很多----从XE5之后,datasnap基本上没有什么变化。

下面继续写下之前说的TDSClientCallbackChannelManager注册回调。

用TDSClientCallbackChannelManager注册一个回调后,会产生一个新的连接,如果再调用TDSClientCallbackChannelManager.NotifyXXXX 或 BroadcastXXXX的时候,还会新建一个连接,然后再断开,并且TDSClientCallbackChannelManager在取消注册的回调时,会产生连接,断开,连接,断开至少两次。所以频繁的用TDSClientCallbackChannelManager发送消息,会频繁的产生连接,断开,对服务程序会有一定的影响。

所以使用TDSClientCallbackChannelManager的时机,应该是“只为了被动的接收其它客户端或服务端的消息通知”,这种方式实际上属于“服务端推送”的方式。当程中已经有一个用SQLConnection建立的连接时,更不要用TDSClientCallbackChannelManager去发送信息,最好服务器程序中提供类似这样的方式:
    function NotifyCallback(const AChannelName, AClientId, AValue: string): Boolean;
begin
  Result := FDSServer.BroadcastMessage(AChannelName, AClientId,
    TJSonString.Create(AValue));
end
用SQLConnection的现有连接去执行消息的广播,这样就减少了服务程序的连接,断开次数。
----------------------------------------------
学无止境
作者:
男 hzforce ( ) ★☆☆☆☆ -
普通会员
2015/1/22 14:56:47
22楼: 感谢楼主的指教.
现在又有一个新的问题

我现在想使用https协议,我的做法是增加了一个TDSCertFiles控件,然后设置好TDSHTTPService的CertFile属性,然后下载了一个最新openssl,把需要的那两个dll文件放在程序的目录底下,然后我用https://localhost:8081/datasnap/rest/TServerMethods1/ReverseString/abc提交的时候就出现问题了,提示如下:
First chance exception at $7544B760. Exception class EIdOSSLUnderlyingCryptoError with message
'Error accepting connection with SSL.
error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher'.
Process Project1.exe (4656)

First chance exception at $7544B760. Exception class EIdOSSLUnderlyingCryptoError with message
'Error accepting connection with SSL.
error:1408A10B:SSL routines:SSL3_GET_CLIENT_HELLO:wrong version number'.
Process Server_Controller.exe (1224)

我已经换过好几个openssl的版本了,还是不行,请问这个会是什么原因呢?
我的系统是 win7 32位 + XE7 update1
----------------------------------------------
-
作者:
男 hzforce ( ) ★☆☆☆☆ -
普通会员
2015/1/22 16:01:14
23楼: 唉,原来证书问题~~~
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/26 16:32:52
24楼: 今天又发现个问题,TCP连接方式,TDSClientCallbackChannelManager注册回调后,心跳功能不起作用,只有服务端执行回调时,才会触发异常。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/29 0:16:30
25楼: TDSClientCallbackChannelManager注册回调后,服务端的连接线程会被”挂起“
相当于WaitForSingleObject的INFINITE死等,如果网路异常断开,会一直挂着,除非网络连通后,客户端重新注册回调。

所以最好周期性用DSServer.notifyxxxx
去发送一个TJSONNull的消息,用来“激活”线程,线程激活后会产生正常的Disconnect事件。否则挂”死“的线程会起来越多,服务程序早晚会挂了。
----------------------------------------------
学无止境
作者:
男 topok (topok) ★☆☆☆☆ -
盒子活跃会员
2015/1/29 9:22:54
26楼: delphi哪个版本的Datasnap稳定高效,网上有人说XE3的非常不稳定而且还特别慢
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/31 0:54:12
27楼: 发现XE7的一个BUG。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IPPeerClient, Datasnap.DSCommon,
  Vcl.StdCtrls, Data.DBXJSON, System.JSON;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    DSCCM: TDSClientCallbackChannelManager;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
  public
    { Public declarations }
  end;

  TCB = class(TDBXCallback)
  public
    function Execute(const Arg: TJSONValue): TJSONValue; overload; override;
  end;

var
  Form1: TForm1;

  Flag: Boolean;

implementation

{$R *.dfm}

{ TCB }

function TCB.Execute(const Arg: TJSONValue): TJSONValue;
begin
  Result := TJSONTrue.Create;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Flag := DSCCM.RegisterCallback('A', TCB.Create);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if Flag then
    DSCCM.UnregisterCallback('A');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;
end;

end.

注册回调后,再反注册,再注册,再反注册。就会有内存泄漏了。
----------------------------------------------
学无止境
作者:
男 wiseinfo (wisienfo) ▲▲▲▲▲ -
普通会员
2015/1/31 0:59:06
27楼: TO:jjwwang

继续普及下周期性用DSServer.notifyxxxx 线程激活后会产生正常的Disconnect事件
----------------------------------------------
-
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/31 1:01:24
28楼: function TDSClientCallbackChannelManager.RegisterCallback(
  const CallbackId, ChannelNames: string; const Callback: TDBXCallback): Boolean;
var
  Status: Boolean;
  Item, LItem: TDSCallbackItem;
begin
  TMonitor.Enter(FLocalCallbackRepo);
  try
    if not FLocalCallbackRepo.ContainsKey(CallbackId) then
    begin
      Item := TDSCallbackItem.Create(FChannelName, Callback, ChannelNames);
      FLocalCallbackRepo.Add(CallbackId, Item);

      if FLocalCallbackRepo.Count = 1 then
      begin
        FState := ctsStarted;
        FStateError := EmptyStr;

        // start a thread if this is the first registered callback (check if there an active one)
{**********}
        FDSChannelThread := TDSChannelThread.Create(
每注册一次回调就创建一个线程。
{**********}
          procedure
          begin
          try
          ExecuteRemote('DSAdmin', 'ConnectClientChannel',
          procedure (Params: TDBXParameterList)
          begin
          Params[0].Value.AsString := FChannelName;
          Params[1].Value.AsString := FManagerId;


但是:


constructor TDSClientCallbackChannelManager.TDSChannelThread.Create(
  Worker: TDSWorker; Manager: TDSClientCallbackChannelManager);
begin
  FWorker := Worker;
  FManager := Manager;
{**********}
  FreeOnTerminate := False;  
这里不是自动释放线程资源
{**********}
  inherited Create(False); // Suspended
end;


并且:

destructor TDSClientCallbackChannelManager.Destroy;
begin
  try
    // unregister all outstanding callbacks
    CloseClientChannel();
  except
    // igonore I/O errors at this point
  end;
{**********}
TDSClientCallbackChannelManager释放的时候,FDSChannelThread 只释放一次。
{**********888}
  if FDSChannelThread <> nil then
  begin
    FDSChannelThread.WaitFor;
    FDSChannelThread.Free;
  end;
{**********8}

所以多次注册,多次创建了线程,但只有一个能释放。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/31 1:04:08
29楼: 建议:

动态创建TDSClientCallbackChannelManager进行回调注册,取消回调或注册回调失败后,立即释放TDSClientCallbackChannelManager。 再注册的时,重复上面的过程。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/31 1:20:02
30楼: to: wiseinfo (wisienfo)

只是粗略的跟踪了一下D的代码,大体感觉是这样子。
实际上,只要服务程序通知回调的时间周期不是很长的话,应该也没有什么问题。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/31 22:14:03
31楼: 闲暇之余又看了看TDSClientCallbackChannelManager的代码,越想越觉得起火,感觉就是临时对付着写的,EMB的工程师根本没用心。

主要问题:

1 RegisterCallback反回值并不能代码注册回调成功与否。由于在TDSChannelThread线程中向服务程序注册TDBXCallback, 所以RegisterCallback方法的返回值是在线程执行完之前返回的,并不能表时TDSChannelThread注册回调成功了。

2 TDSChannelThread线程中产生异常时,触发TDSClientChannelManagerEvent事件,所以开发者在TDSClientChannelManagerEvent事件中写的代码,即可能是在主线程中,也可能是在TDSChannelThread线程。不注意时,可能给开发者带来麻烦。

3 注销回调的UnregisterCallback方法,要与服务程序 【连接,断开】 2次。 难道就不能用一个连接去让服务程序做事吗?

4 TDSChannelCallback, TDSChannelThread, TDSChannelInvokeEvent都是内部的。 想借用点源代都不容易。
----------------------------------------------
学无止境
作者:
男 jjwwang (jjwwang) ▲▲▲▲▲ -
注册会员
2015/1/31 22:30:53
32楼: 替代方法:

1 用SQLConnection连接Datasnap服务。
2 创建DBXCommand,然后按照TDSClientCallbackChannelManager下面的代码片段的方式,自己写代码注册回调,
          ExecuteRemote('DSAdmin', 'ConnectClientChannel',
          procedure (Params: TDBXParameterList)
          begin
          Params[0].Value.AsString := FChannelName;
          Params[1].Value.AsString := FManagerId;
          Params[2].Value.AsString := CallbackId;
          Params[3].Value.AsString := ChannelNames;
          Params[4].Value.AsString := FSecurityToken;
          Params[5].Value.SetCallbackValue(FChannelCallback);
          end,
补充说明:
如果Datasnap的服务程序上的ChannelName中已经存在ClientID客户端标识的话,注册是不会成功的,所以 [不是] 首次注册回调时,要先从服务端清除掉之前的ChannelName中的ClientID.具体方法,参照TDSClientCallbackChannelManager中注销回调的源代码。

这样一来,可以避免多次和服务程序的 连接,断后次数,减轻服务程序的压力。
----------------------------------------------
学无止境
作者:
男 lulugo (找上帝说理) ▲▲▲▲▲ -
盒子活跃会员
2015/2/1 16:51:29
33楼: 关注
----------------------------------------------
-努力成就未来....
作者:
男 wuxi15 (似水·流年) ▲▲▲△△ -
注册会员
2015/2/1 17:22:41
34楼: 顶楼主。
各位想了解Datasnap客户端互动的可以看一下XE6的Demo。XE7下很多Demo没有了。
XE6下的Demo比较齐全,客户端与服务端通讯,客户端与客户端通讯都有
----------------------------------------------
-
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v2.1 版权所有 页面执行52.73438毫秒 RSS