标题: |
|
加入我的收藏 |
楼主: |
首先声明,开此贴仅并不是想说明Datasnap的好与不好,也不包含其它任何比较之意,仅是靠个人对Delphi的喜爱而开此贴。
限于各种原因,本人并没有什么Datasnap的实际经验,只是业余时间喜欢“把玩”--写些小DEMO测试,所以很抱歉,无法提供Datasnap的一些性能测试的数据,仅能提供少许写DEMO时的体会,即希望能抛砖引玉,也希望对刚接触Datasnap的新人有所帮助。若有不正确之处,欢迎指出。
如果“脏”了谁的眼,勿喷,请绕行。
----------------------------------------------
学无止境 |
作者: |
|
2015-1-13 12:21:52 |
1楼: |
鄙人将整理一些之前的DEMO,找时间上来补充----虽然有些可能在网上能搜到。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-13 12:30:28 |
2楼: |
呵呵顶一个,
----------------------------------------------
-
|
作者: |
|
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(回调)更好,还可以在客户端显示进度。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-13 13:29:43 |
5楼: |
为了回复你,我登录了一下系统,支持!!!
----------------------------------------------
-
|
作者: |
|
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%的返回时间。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-13 13:40:50 |
4楼: |
好帖,顶楼主!
----------------------------------------------
==========
|
作者: |
|
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是比较重要的,在传一些对象参数时,需要注意。后面我会写一些经验,但还是要靠个人摸索掌握。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-13 14:06:36 |
7楼: |
难得有这种好帖子。支持一下。
----------------------------------------------
虽千万人吾往矣!
|
作者: |
|
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的实例,这和网上写的监控客户端连接是一个意思。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-13 14:46:47 |
8楼: |
除此之外,涉及到数据库的,自觉使用连接池(自己实现也好,用fdac的也好) 一般的数据查询,使用DBX framework时,返回DBXReader最好了。(目前未与直接返回TDataset对比过,感觉dataset最终也会转为dbxreader)
慎用TDSClientCallbackChannelManager的注册回调,除非只是为了接收其它客户端或服务器端的消息。 如果有SQLConnection产生一个连接了, 不要用TDSClientCallbackChannelManager的BroadcastToChannel或NotifyCallback方法,而要通过SQLConnection调用服务器的一个方法进行广播或通知其它客户端----原因后续再说明。
----------------------------------------------
学无止境
|
作者: |
|
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楼: |
顶一下
----------------------------------------------
-
|
作者: |
|
2015-1-14 8:00:03 |
12楼: |
对初学者有意义的,支持
----------------------------------------------
-
|
作者: |
|
2015-1-14 9:41:17 |
13楼: |
将帖子标注为好帖,将LZ标注为高手
----------------------------------------------
-
|
作者: |
|
2015-1-14 9:44:10 |
14楼: |
好贴 支持lZ
----------------------------------------------
-
|
作者: |
|
2015-1-15 14:08:36 |
15楼: |
顶。
----------------------------------------------
虽千万人吾往矣!
|
作者: |
|
2015-1-15 19:23:37 |
16楼: |
是的,请有过实际案例的把情况发上来,讨论讨论
----------------------------------------------
-
|
作者: |
|
2015-1-18 10:37:52 |
17楼: |
好贴! 另外请教一个问题:
datasnap服务器上的方法,如果用http方式请求的话,他会返回 {"result":[{"name":"123","dfd":1}]} 这种格式,能有办法让他直接返回 {"name":"123","dfd":1} 这样的格式吗?
----------------------------------------------
-
|
作者: |
|
2015-1-18 11:09:15 |
18楼: |
好!!!
----------------------------------------------
-
|
作者: |
|
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"]
----------------------------------------------
学无止境
|
作者: |
|
2015-1-18 15:36:55 |
20楼: |
每天来看看楼主有没有更新,谢谢!
----------------------------------------------
-
|
作者: |
|
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的现有连接去执行消息的广播,这样就减少了服务程序的连接,断开次数。
----------------------------------------------
学无止境
|
作者: |
|
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
----------------------------------------------
-
|
作者: |
|
2015-1-22 16:01:14 |
23楼: |
唉,原来证书问题~~~
----------------------------------------------
-
|
作者: |
|
2015-1-26 16:32:52 |
24楼: |
今天又发现个问题,TCP连接方式,TDSClientCallbackChannelManager注册回调后,心跳功能不起作用,只有服务端执行回调时,才会触发异常。
----------------------------------------------
学无止境
|
作者: |
|
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的非常不稳定而且还特别慢
----------------------------------------------
-
|
作者: |
|
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.
注册回调后,再反注册,再注册,再反注册。就会有内存泄漏了。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-31 0:59:06 |
27楼: |
TO:jjwwang
继续普及下周期性用DSServer.notifyxxxx 线程激活后会产生正常的Disconnect事件
----------------------------------------------
-
|
作者: |
|
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}
所以多次注册,多次创建了线程,但只有一个能释放。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-31 1:04:08 |
29楼: |
建议:
动态创建TDSClientCallbackChannelManager进行回调注册,取消回调或注册回调失败后,立即释放TDSClientCallbackChannelManager。 再注册的时,重复上面的过程。
----------------------------------------------
学无止境
|
作者: |
|
2015-1-31 1:20:02 |
30楼: |
to: wiseinfo (wisienfo)
只是粗略的跟踪了一下D的代码,大体感觉是这样子。 实际上,只要服务程序通知回调的时间周期不是很长的话,应该也没有什么问题。
----------------------------------------------
学无止境
|
作者: |
|
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都是内部的。 想借用点源代都不容易。
----------------------------------------------
学无止境
|
作者: |
|
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中注销回调的源代码。
这样一来,可以避免多次和服务程序的 连接,断后次数,减轻服务程序的压力。
----------------------------------------------
学无止境
|
作者: |
|
2015-2-1 16:51:29 |
33楼: |
关注
----------------------------------------------
-努力成就未来....
|
作者: |
|
2015-2-1 17:22:41 |
34楼: |
顶楼主。 各位想了解Datasnap客户端互动的可以看一下XE6的Demo。XE7下很多Demo没有了。 XE6下的Demo比较齐全,客户端与服务端通讯,客户端与客户端通讯都有
----------------------------------------------
我爱Delphi,我用Delphi http://www.cnblogs.com/wuxi15 |
|