datasnap的初步
datasnap的初步-回调函数
服务器端
TServerMethods1 =class(TComponent) private { Private declarations } public { Public declarations } functionTest(funcCallBack: TDBXCallback):boolean; end; functionTServerMethods1.Test(funcCallBack: TDBXCallback):boolean; begin funcCallBack.Execute(TJSONNumber.Create()).Free; sleep(); Result :=True; end;
客户端,这个必须继承自TDBXCallback
TFuncCallback =class(TDBXCallback) functionExecute(constArg: TJSONValue): TJSONValue;override; end; functionTFuncCallback .Execute(constArg: TJSONValue): TJSONValue; var i:Integer; begin i := TJSONNumber(Arg).AsInt;//可以的到服务器回调来的参数 Result := TJSONNull.Create; end; procedureTForm2.Button1Click(Sender: TObject); begin ClientModule1.ServerMethods1Client.Test(funcCallBack); end; initialization funcCallBack:= TFuncCallBack.Create; finalization //FreeAndNil(funcCallBack);
到此,实现了最基本的回叫。
D2010起提供了DSClientCallbackChannelManager这个控件,这是为了实现一次注册,多次回叫,使用方法很简单
1。客户端使用 DSClientCallbackChannelManager注册回叫函数
function RegisterCallback(const CallbackId: String; const Callback: TDBXCallback): boolean; overload;
DSClientCallbackChannelManager控件带有一个ChannelName的属性,用于CallbackId分组用。ManagerId属性,可理解为ClientId,ClientId必须是唯一的,相同的ClientId,会被认为是相同地点来的连接。
不明白为啥 DSClientCallbackChannelManager要自己设置连接属性,而不是走TSQLConnection。
2。服务器端是TDSServer来做事,它有
两个函数
function BroadcastMessage(const ChannelName: String; const Msg: TJSONValue; const ArgType: Integer = TDBXCallback.ArgJson): boolean; overload; function BroadcastMessage(const ChannelName: String; const CallbackId: String; const Msg: TJSONValue; const ArgType: Integer = TDBXCallback.ArgJson): boolean; overload;
第二个是回调ChannelName里面指定的CallBackId
服务器上用GetAllChannelCallbackId能返回在某个ChannelName里面所有的CallbackId。
到此,我们就能使用DSClientCallbackChannelManager来制作一个简单的聊天软件,并能实现私聊的功能。但是如何处理,聊天用户掉线的问题,就比较麻烦了。
和RO比较,这设计有些像RO里的TROEventReceiver,但远没RO灵活, TROEventReceiver直接就能订阅(RegisterEventHandlers)上一堆服务器的事件,DataSnap却要定义一堆的TDBXCallback。
datasnap的初步 TDSClientCallbackChannelManager的工作方式
理解一下TDSClientCallbackChannelManager的工作方式吧
客户端调用RegisterCallback,其实就是开始了一个线程TDSChannelThread,该线程起一个dbxconnection,连接到服务器上,执行DSAdmin.ConnectClientChannel,服务器上的这个DSAdmin.ConnectClientChannel很神奇,所有的数据传输都在这里了,这个连接不会关闭,以做到服务器往客户端push数据。TDSClientCallbackChannelManager只能做到服务器向客户端推送数据,单向的。客户端要向服务器送数据,走TDSAdminClient的NotifyCallback方法,也就是只有经过SQLConnection。
DSAdmin(在Datasnap.DSCommonServer单元)是TDBXServerComponent(在Datasnap.DSPlatform单元)的子类,ConnectClientChannel函数直接call ConsumeAllClientChannel。
代码摘抄
functionTDBXServerComponent.ConsumeAllClientChannel(constChannelName, ChannelId, CallbackId, SecurityToken:String; ChannelNames: TStringList; ChannelCallback: TDBXCallback; Timeout:Cardinal):Boolean; ... begin // wait for exit message repeat //这里开始 Data :=nil; IsBroadcast :=false; ArgType := TDBXCallback.ArgJson; QueueMessage :=nil; TMonitor.Enter(CallbackTunnel.Queue); try {Wait for a queue item to be added if the queue is empty, otherwise don't wait and just pop the next queue item} ifCallbackTunnel.Queue.QueueSize =0then IsAquired := TMonitor.Wait(CallbackTunnel.Queue, Timeout) else IsAquired :=true; ifIsAquiredand(CallbackTunnel.Queue.QueueSize >)then begin {Get the next queued item from the tunnel} QueueMessage := CallbackTunnel.Queue.PopItem; Data := QueueMessage.Msg; IsBroadcast := QueueMessage.IsBroadcast; ArgType := QueueMessage.ArgType; end; finally TMonitor.Exit(CallbackTunnel.Queue); end; ifIsAquiredand(Data <>nil)then ifIsBroadcastthen begin try Msg := TJSONObject.Create(TJSONPair.Create('broadcast', TJSONArray.Create(Data).Add(ArgType))); if(QueueMessage.ChannelName <> EmptyStr)and (QueueMessage.ChannelName <> CallbackTunnel.ServerChannelName)then Msg.AddPair(TJSONPair.Create('channel', QueueMessage.ChannelName)); try ChannelCallback.Execute(Msg).Free; except try // Remove the callback tunnel from the list, it will be freed at the end of this method InternalRemoveCallbackTunnel(DSServer, CallbackTunnel); except end; raise; end; finally QueueMessage.InstanceOwner :=false; FreeAndNil(QueueMessage); end; end elseifAssigned(QueueMessage)then begin TMonitor.Enter(QueueMessage); try Msg := TJSONObject.Create( TJSONPair.Create('invoke', TJSONArray.Create( TJSONString.Create(QueueMessage.CallbackId), Data).Add(ArgType))); try QueueMessage.Response := ChannelCallback.Execute(Msg); except onE : Exceptiondo begin QueueMessage.IsError :=True; QueueMessage.Response := TJSONObject.Create(TJSONPair.Create('error', E.Message)); ifChannelCallback.ConnectionLostthen begin WasConnectionLost :=True; TMonitor.Pulse(QueueMessage); try // Remove the callback tunnel from the list, it will be freed at the end of this method InternalRemoveCallbackTunnel(DSServer, CallbackTunnel); except end; Break; end; end; end; TMonitor.Pulse(QueueMessage); finally TMonitor.Exit(QueueMessage); end; end until(notIsAquired)or(Data =nil);//这里结束 ... end.
客户端调用UnregisterCallback,调用的线程(一般就是主线程),直接起一个dbxconnection,让服务器执行DSAdmin.UnregisterClientCallback,执行后立刻dbxconnection.close, 服务器执行UnregisterClientCallback只是剔除消息筛选;
如果客户端没有订阅别的Callback,就再次起一个dbxconnection,让服务器执行DSAdmin.CloseClientChannel,服务器的CloseClientChannel里,会往CallbackTunnel广播一个nil的消息,这就让ConnectClientChannel的repeat循环也会退出(data=nil),从而关闭最开始连接。
另外, TDSClientCallbackChannelManager在界面上找不到输入认证信息的地方,比如DSAuthPassword, DSAuUser等,其实TDSClientCallbackChannelManager也好SQLConnection也好,他们都是个载体罢了,真正在后面起作用的,是TDBXProperties。监视一下DSServer的OnConnect里的DSConnectEventObject.ConnectProperties,我们就能知道TDSClientCallbackChannelManager的username, password,其实是SQLConnection的DSAuthPassword,DSAuUser。
在TDBXProperties里的键值对为
DSAuthenticationUser=
DSAuthenticationPassword=
最后,TDSClientCallbackChannelManager并没有Filters的属性,这个其实现在的客户端,就算使用SQLConnection时也不必设置Filters了,我们只要别忘记在客户端也TTransportFilterFactory.RegisterFilter一下要用的Filter即可。
datasnap的初步 序列化自己写的类
今天在网上找到了一个marshall和unmarshall的例子,将自己的定义的类,序列号json对象
uses DBXJSONReflect, DBXJSON TPerson = class FirstName: String; LastName: String; Age: Integer; end;
procedureTForm1.Button1Click(Sender: TObject); var Mar: TJSONMarshal;//序列化对象 UnMar: TJSONUnMarshal;// 反序列化对象 person: TPerson;//我们自定义的对象 SerializedPerson: TJSONObject;//Json对象 begin Mar := TJSONMarshal.Create(TJSONConverter.Create); try person := TPerson.Create; try person.FirstName :='Nan'; person.LastName :='Dong'; person.Age :=; SerializedPerson := Mar.Marshal(person)asTJSONObject; finally FreeAndNil(person); end; finally Mar.Free; end; // show一下person的json对象的信息 ShowMessage(SerializedPerson.ToString); end; 反序列化 //UnMarshalling UnMar := TJSONUnMarshal.Create; try person := UnMar.UnMarshal(SerializedPerson)asTPerson; try // 我们用断言检查一下,unmarshal后的信息完全正确。 Assert(person.FirstName ='Nan'); Assert(person.LastName ='Dong'); Assert(person.Age =); finally person.Free; end; finally UnMar.Free; end;
datasnap的初步 生命期LifeCycle
TDSServerClass有一个属性LifeCycle,这个属性有三个值,很好理解
1.Session,这是默认值。
就是一个连接,一个Session,一个Session的意思就是连接上来后,服务器端就创建一个DSServerClassGetClass里返回的PersistentClass一个实例,并一直保持到连接断开,所有这期间的ServerMethod调用,都是这个实例的调用。所以这是线程安全的。
2.Server
顾名思义,就是全局就一个PersistentClass的实例,所有的连接Call上来的ServerMethod都是这唯一实例的调用,单例模式,当然,这也就不是线程安全的,需要自己来实现线程安全。
3.Invocation
这个更细,每次ServerMethod的Call,都将创建和销毁一PersistentClass的实例。由于创建销毁比较耗资源,可以操作TDSServerClass的OnCreateInstance和OnDestroyInstance事件,在这两个事件里面做缓存池。代码如下
procedureTServerContainer1.DSServerClass1CreateInstance( DSCreateInstanceEventObject: TDSCreateInstanceEventObject); begin DSCreateInstanceEventObject.ServerClassInstance := 缓存池取一个实例 end; procedureTServerContainer1.DSServerClass1DestroyInstance( DSDestroyInstanceEventObject: TDSDestroyInstanceEventObject); begin 将DSCreateInstanceEventObject.ServerClassInstance的实例还给缓存池 end;
缓存池的实现很简单了,就不写了。
datasnap的初步 TDSAuthenticationManager的用法
xe开始有了TDSAuthenticationManager,这个主要用来做用户认证,用法也很简单
服务器端
1.TDSAuthenticationManager有两个主要的事件
在这个事件里面,看看检测连上来的用户名,密码是否合法,valid如果置为false,这就为非法连接了,DSServer会立刻抛出异常后close连接。
另外,UserRoles的设计,我觉得比RO高明。
procedureTServerContainer1.DSAuthenticationManager1UserAuthenticate( Sender: TObject;constProtocol, Context, User, Password:string; varvalid:Boolean; UserRoles: TStrings); begin valid := User ='zyz'; ifUser ='admin'then UserRoles.Add('admins'); end;
在这个事件里面,判断已经连接上来的用户,对ServerMethod的调用是否合法,注视里也写了,默认是如何检测是否合法的。
procedureTServerContainer1.DSAuthenticationManager1UserAuthorize( Sender: TObject; EventObject: TDSAuthorizeEventObject; varvalid:Boolean); begin { TODO : Authorize a user to execute a method. Use values from EventObject such as UserName, UserRoles, AuthorizedRoles and DeniedRoles. Use DSAuthenticationManager1.Roles to define Authorized and Denied roles for particular server methods. } //valid := True; end;
上面我说UserRoles的设计比较高明,主要还是因为这个UserRole的设计用到了java的那种注释类的技术,比如服务器上这么定义一个方法
[TRoleAuth('admins')] functionEchoString(Value:string):string;
这样定义后,就算不写DSAuthenticationManager1UserAuthorize,TDSAuthenticationManager也会自动帮你检查该角色是否有权利调用该ServerMethod。RTTI估计是学Java的Annotation才增加了TCustomAttribute。
2.客户端
客户端很简单了,设置SQLConnection的DSAuthUser和DSAuthPassword就行了。
datasnap的初步 对象的销毁
TServerMethods1Client继承自TDSAdminClient,这个类的构造函数
constructor Create(ADBXConnection: TDBXConnection); overload;
后面的AInstanceOwner参数,挺重要,理解这个,对于避免内存泄漏有很大好处。
默认情况下,我们使用Create来创建ServerMethodClient,也就AInstanceOwner为true了,也就是所有进入 TServerMethods1Client类方法的参数,都被ServerMethodClient给来释放。我觉得EMBT推荐使用 AInstanceOwner=True。
DATASNP如何释放内存,请看代码
客户端
procedureTDBXCommand.CommandExecuting; begin ifAssigned(FFreeOnCloseList)then FreeOnExecuteObjects;//这里释放 Open; CloseReader; if(FParameters <>nil)and(FParameters.Count >)then begin ifnotFPreparedthen Prepare; SetParameters; end; end;
也就是说,对每个DBXCommand,每次执行前都会清理上一回留下的垃圾。当然最后的垃圾肯定要等到DBXCommand.Close时才去清理了。
对于function(a: TA, out b: TB): TA这样的调用
AInstanceOwner为true时,a, b, 以及返回值result我们都不用去自己Free。尤其要注意入口参数a,可能进去执行后立刻被Free(需要被Marshal的类),也可能是等到下次Call时被Free(比如TStream)。
反之,则都需要自己去free。但是TDBXCallback,是个例外,就算AInstanceOwner为False,也不能自己Free。
服务器端
procedureTDSMethodValues.AssignParameterValues( Parameters: TDBXParameterArray); begin ClearReferenceParameters;//这里清理 ifLength(FMethodValues) <> Length(Parameters)then begin SetLength(FMethodValues, Length(Parameters)); SetLength(FAllocatedObjects, Length(Parameters)); end;
也是一样的,每回清理前一回留下的垃圾,最后的也是客户端调用DBXCommand.Close时服务器收到"command_close"时被清理,当然服务器自己关闭DBXCommand时也会清理的。
从这个规则,也能看出,客户端,如果要多线程访问服务器,要么访问服务器时聚集到一起,用关键区或者信号量控制同时只有一个线程能上服务器,要么起多个连接。以避免A线程正读的欢呢,B线程就去Call同样的ServerMethod了,把返回结果给Free了。
最后读读EMBT的帖子吧
datasnap的初步 内存泄漏的原因
终于找到了datasnap内存泄漏的原因了,只要你写了下面的代码,肯定出现内存泄漏,无论是session还是invocation。我表示很悲痛。
procedureTServerContainer1.DSServerClass1CreateInstance( DSCreateInstanceEventObject: TDSCreateInstanceEventObject); begin // end; procedureTServerContainer1.DSServerClass1DestroyInstance( DSDestroyInstanceEventObject: TDSDestroyInstanceEventObject); begin // end;
Help里面写道
DSServer.TDSServerClass.OnCreateInstance
Happens upon creation of server class instances.
Use this event to override the default creation of server class instances. This allows for custom initialization and custom object pooling if the LifeCycle property is set to TDSLifeCycle.Invocation.
是说只有在Invocation才使用这两个事件。可session模式下就算写了,也不应该内存泄漏吧。再说了,invocation模式下,这个函数啥也不干,还是是泄漏了。
datasnap的初步 关于TDSTCPServerTransport的Filters
TDSTCPServerTransport的Filter属性,可以对传递的数据进行加密,压缩,再修改等,有点注入的概念。默认情况下,Datasnap自带的ZLIB, PC1,RSA三个Filter。测试了一下,RSA只对KEY加密,PC1才对内容加密,ZLIB来做压缩,ZLIB压缩实在不咋的。并且,Filter的顺序,是依次执行的。我现在打算实现,服务器的一个Log功能,记录下来进入的数据,出去的数据,要求记录下来的数据是明文。
TTransportFilter的ProcessInput,ProcessOutput光看名字比较费解,可以这么理解ProcessInput为编码,ProcessOutput可以理解为解码。
首先给DSTCPServerTransport1的Fitlers都加上默认的3个Filter。
上一个完整的代码
unituLogFilter; interface uses SysUtils, DBXPlatform, DBXTransport; type TLogHeadFilter =class(TTransportFilter) public constructorCreate;override; destructorDestroy;override; functionProcessInput(constData: TBytes): TBytes;override; functionProcessOutput(constData: TBytes): TBytes;override;//do nothing functionId: UnicodeString;override; end; TLogTailFilter =class(TTransportFilter) public constructorCreate;override; destructorDestroy;override; functionProcessInput(constData: TBytes): TBytes;override;//do nothing functionProcessOutput(constData: TBytes): TBytes;override; functionId: UnicodeString;override; end; procedureAddLogFilter(Filters: TTransportFilterCollection); implementation uses CodeSiteLogging; const LogFilterName_Tail ='LogTail'; LogFilterName_Head ='LogHead'; procedureAddLogFilter(Filters: TTransportFilterCollection); var fs: TDBXStringArray; i:Integer; begin fs := Filters.FilterIdList; Filters.Clear; Filters.AddFilter(LogFilterName_Head); fori := Low(fs)toHigh(fs)do begin Filters.AddFilter(fs[i]); end; Filters.AddFilter(LogFilterName_Tail); end; constructorTLogTailFilter.Create; begin inheritedCreate; //CodeSite.Send(csmBlue, 'TLogTailFilter.Create'); end; destructorTLogTailFilter.Destroy; begin //CodeSite.Send(csmBlue, 'TLogTailFilter.Destroy'); inheritedDestroy; end; functionTLogTailFilter.ProcessInput(constData: TBytes): TBytes; begin Result := Data; CodeSite.Send(csmOrange,'To Client: '+ IntToStr(Length(Data))); end; functionTLogTailFilter.ProcessOutput(constData: TBytes): TBytes; begin Result := Data; CodeSite.Send(csmOrange,'From Client: '+ IntToStr(Length(Data)), TEncoding.ASCII.GetString(Data)); end; functionTLogTailFilter.Id: UnicodeString; begin Result := LogFilterName_Tail; end; { TLogInputFilter } constructorTLogHeadFilter.Create; begin inherited; //CodeSite.Send(csmBlue, 'TLogHeadFilter.Create'); end; destructorTLogHeadFilter.Destroy; begin //CodeSite.Send(csmBlue, 'TLogHeadFilter.Destroy'); inherited; end; functionTLogHeadFilter.Id: UnicodeString; begin Result := LogFilterName_Head; end; functionTLogHeadFilter.ProcessInput(constData: TBytes): TBytes; begin Result := Data; CodeSite.Send(csmYellow,'To Client: '+ IntToStr(Length(Data)), TEncoding.ASCII.GetString(Data)); end; functionTLogHeadFilter.ProcessOutput(constData: TBytes): TBytes; begin Result := Data; CodeSite.Send(csmYellow,'From Client: '+ IntToStr(Length(Data))); end; initialization TTransportFilterFactory.RegisterFilter(LogFilterName_Tail, TLogTailFilter); TTransportFilterFactory.RegisterFilter(LogFilterName_Head, TLogHeadFilter); finalization TTransportFilterFactory.UnregisterFilter(LogFilterName_Tail); TTransportFilterFactory.UnregisterFilter(LogFilterName_Head); end.
这个unit实现了上面的功能,
数据进入服务器时,DataSnap的Reader读出时按顺序经过Filter进行解码,最后的Filter,也就是这里的TLogTailFilter的ProcessOutput出来的肯定应该是明文了,记录下来。
数据出服务器时, DataSnap的Writer写数据时,也按顺序经过Filter进行编码,刚开始的肯定是明文的,也就是TLogHeadFilter的ProcessInput了,记录下来。
要使用这个unit,只要在ServerContainerUnit1单一的OnCreate里面写入即可。如下
procedure TServerContainer1.DataModuleCreate(Sender: TObject); begin AddLogFilter(DSTCPServerTransport1.Filters); end;
最后,上个图,看看client和服务器之间的通讯是怎样的。
为方面看,我分开了,第一次是connect,然后二次调用了EchoString。可以看出一次servermethod,有3个来回的交流(一个prepare, 一个execute,一个command_close。prepare和command_close并不是每次必须的,这里因为我是每次都创建新的TServerMethods1Client),并且交流的数据都是JSON的整列。这里也打印出了编码解码前数据长度,以及编码解码后的数据长度,如果要测试ZLIB的压缩效果,可以参考。
或许要说DSServer的OnTrace事件也可以玩,但是 OnTrace只能记录Client进Server的数据,对出去的数据TRACE不到的,很遗憾。
最后,有一些其他的现成的开源Filter可用,尤其是压缩的,去http://code.google.com/p/dsfc/
datasnap的初步 直接返会自定义类
前面我说datasnap不支持自定义类型是错误的。其实datasnap一旦发现是自定义类型,就会自动用json给marshall了,今天的测试代码如下。
服务器端
functionTServerMethods1.GetPerson: TPerson; begin Result := TPerson.Create; Result.FirstName :='zyz'; Result.LastName :='Jacky'; Result.Age :=; end;
客户端,让SQLConnection自动产生代理代码,可以的到
functionTServerMethods1Client.GetPerson: TPerson; begin ifFGetPersonCommand =nilthen begin FGetPersonCommand := FDBXConnection.CreateCommand; FGetPersonCommand.CommandType := TDBXCommandTypes.DSServerMethod; FGetPersonCommand.Text :='TServerMethods1.GetPerson'; FGetPersonCommand.Prepare; end; FGetPersonCommand.ExecuteUpdate; ifnotFGetPersonCommand.Parameters[].Value.IsNullthen begin FUnMarshal := TDBXClientCommand(FGetPersonCommand.Parameters[].ConnectionHandler).GetJSONUnMarshaler; try Result := TPerson(FUnMarshal.UnMarshal(FGetPersonCommand.Parameters[].Value.GetJSONValue(True))); ifFInstanceOwnerthen FGetPersonCommand.FreeOnExecute(Result); finally FreeAndNil(FUnMarshal) end end else Result :=nil; end;
也就是说,客户端也会自动给unmarshal的。
调用代码
procedureTForm1.btn3Click(Sender: TObject); var p: TPerson; begin p := FServerMethod.GetPerson; withpdo ShowMessage(Format('FirstName=%s, LastName=%s, Age=%d', [FirstName, LastName, Age])); //p.Free; end;
由于我的FInstanceOwner使用的默认为true,UnMarshal的CreateObject产生的类,让Datasnap自己去释放了,所以p.free要注视掉。释放的时机有两个:
1。下一次调用到来
2。DBXCommand.Close
datasnap的初步 获得客户端的信息
记得datasnap 2009时,要得到客户端信息,非官方的方法,要去搞什么DSConnectEventObject.ChannelInfo.Id,弄成 TIdTCPConnection。xe2就好得多了。
仍然是在DSServer的OnConnect 事件里,
DSConnectEventObject.ChannelInfo.ClientInfo就是客户端的信息。能得到啥?
看代码
TDBXClientInfo =record IpAddress:String; ClientPort:String; Protocol:String; AppName:String; end;
也就是能取得客户端ip,端口,连接协议,不过AppName这玩意儿一直是空的。
执行到 DSServer的OnConnect的事件里,其实socket已经完全连上了,client已经调用了server的connect方法了,在这个方法里触发的OnConnect。所以DSServer的OnConnect其实并不是真的socket的OnConnect
datasnap的初步 Session的管理
Datasnap的session管理是强制的,没有选项能说不要。
管理靠一单例TDSSessionManager来管理。对于目前说到TDSTCPServerTransport,建立的的Session为TDSTCPSession,它是TDSSession的子类。
Session在开始连接后,就创建了,再连接断开后消亡。
TDSSession =class private FStartDateTime: TDateTime; /// creation timestamp FDuration:Integer; /// in miliseconds, for infinite (default) FStatus: TDSSessionStatus; /// default idle FLastActivity:Cardinal; /// timestamp of the last activity on this session FUserName:String; /// user name that was authenticated with this session FSessionName:String; /// session name, may be internally generated, exposed to 3rd party FMetaData: TDictionary<STRING,STRING>;/// map of metadata stored in the session FMetaObjects: TDictionary<STRING,TOBJECT>;/// map of objects stored in the session FUserRoles: TStrings; /// user roles defined through authentication FCache: TDSSessionCache; FLastResultStream: TObject; /// Allow any object which owns a stream, or the stream itself, to be stored here FCreator: TObject; /// Creator of the session object reference
可以看出,Session可以用存储了很多东西 。用得多的是FMetaData与FMetaObjects
对于字符串,PutData放进去,GetData取出来;对于Object,PutObject放进去,GetObject取出来。
使用方法为
TDSSessionManager.GetThreadSession.PutData('userid', userId); userId := TDSSessionManager.GetThreadSession.GetData('userid');
另外,放入FMetaObjects的Object,Session的Free时,会自动帮忙Free,所以不必自己去Free的。
关于Session的超时,
这里自然就想到了TDSTCPServerTransport的KeepAliveInterval和KeepAliveTime属性,这两个属性,其实和Session管理没关系。
跟踪代码,这两个属性的反应在IdStackWindows.pas的
procedureTIdStackWindows.SetKeepAliveValues(ASocket: TIdStackSocketHandle; constAEnabled:Boolean;constATimeMS, AInterval:Integer); var ka: tcp_keepalive; Bytes: DWORD; begin // SIO_KEEPALIVE_VALS is supported on Win2K+ only ifAEnabledand(Win32MajorVersion >=)then begin ka.onoff :=; ka.keepalivetime := ATimeMS; ka.keepaliveinterval := AInterval; WSAIoctl(ASocket, SIO_KEEPALIVE_VALS, @ka, SizeOf(ka),nil,, @Bytes,nil,nil); endelsebegin SetSocketOption(ASocket, Id_SOL_SOCKET, Id_SO_KEEPALIVE, iif(AEnabled,,)); end; end;
里,其实就是简单设置了一下socket fd的属性,所以说TDSSessionManager毛关系都没有。
另外, KeepAliveTime默认值为300000,也就是300秒,KeepAliveInterval默认值为100,这是啥意思呢。KeepAliveTime是sockfd最后一次通讯后,等待了的时间,如果300秒内没通讯,socket栈就自己开始发送心跳探测了,如果每次都没回答,就每隔KeepAliveInterval毫秒问一次。至于问多少次认为是网络断开了,根据Windows OS来定的,windows 2000, 2003是5次,vista以后问10次。也就是说,根据TDSTCPServerTransport的默认设定,网络断了,在win7上,要300+0.1*10,也即是301秒才知道网络断了。
OS的系统设定更长,没数据通讯后2小时才开始探测,每隔1秒探测一回。
SIO_KEEPALIVE_VALS值Windows的OS独有的,Unix还是用SO_KEEPALIVE。
跑题远了,回到正题。如何监控Session呢,TDSSessionManager提供了方法给你插入监听事件。
上代码
var event: TDSSessionEvent; initialization event :=procedure(Sender: TObject; constEventType: TDSSessionEventType; constSession: TDSSession) begin caseEventTypeof SessionCreate: begin LogInfo('SessionCreate'); LogInfo(Format('SessionName=%s', [Session.SessionName])); end; SessionClose: begin LogInfo('SessionClose'); LogInfo(Format('SessionName=%s', [Session.SessionName])); end; end; end; TDSSessionManager.Instance.AddSessionEvent(event); finalization TDSSessionManager.Instance.RemoveSessionEvent(event);
这样就可以了,有多少事件都可以插入监听。
datasnap的初步的更多相关文章
- datasnap的初步 生命期LifeCycle
datasnap的初步 生命期LifeCycle TDSServerClass有一个属性LifeCycle,这个属性有三个值,很好理解1.Session,这是默认值.就是一个连接,一个Sessio ...
- DataSnap初步二
转:https://blog.csdn.net/a00553344/article/details/51670486 1. 一个典型的DataSnap服务器至少需要三个控件: TDSServer: D ...
- 移动端之Android开发的几种方式的初步体验
目前越来越多的移动端混合开发方式,下面列举的大多数我都略微的尝试过,就初步的认识写个简单的心得: 开发方式 开发环境 是否需要AndroidSDK 支持跨平台 开发语言&技能 MUI Win+ ...
- CSharpGL(29)初步封装Texture和Framebuffer
+BIT祝威+悄悄在此留下版了个权的信息说: CSharpGL(29)初步封装Texture和Framebuffer +BIT祝威+悄悄在此留下版了个权的信息说: Texture和Framebuffe ...
- Android自定义View初步
经过上一篇的介绍,大家对于自定义View一定有了一定的认识,接下来我们就以实现一个图片下显示文字的自定义View来练习一下.废话不多说,下面进入我们的正题,首先看一下我们的思路,1.我们需要通过在va ...
- 初步认识Node 之Node为何物
很多人即便是在使用了Node之后也不知道它到底是什么,阅读完本文你应该会有一个初步的.具体的概念了. Node的目标 提供一种简单的构建可伸缩网络程序的方法.那么,什么是可伸缩网络程序呢?可伸缩 ...
- [入门级] 基于 visual studio 2010 mvc4 的图书管理系统开发初步 (二)
[入门级] 基于 visual studio 2010 mvc4 的图书管理系统开发初步 (二) Date 周六 10 一月 2015 By 钟谢伟 Category website develop ...
- 基于C/S架构的3D对战网络游戏C++框架_05搭建系统开发环境与Boost智能指针、内存池初步了解
本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...
- Azure底层架构的初步分析
之所以要写这样的一篇博文的目的是对于大多数搞IT的人来说,一般都会对这个topic很感兴趣,因为底层架构直接关乎到一个公有云平台的performance,其实最主要的原因是我们的客户对此也非常感兴趣, ...
随机推荐
- 高并发情况下分布式全局ID
1.高并发情况下,生成分布式全局id策略2.利用全球唯一UUID生成订单号优缺点3.基于数据库自增或者序列生成订单号4.数据库集群如何考虑数据库自增唯一性5.基于Redis生成生成全局id策略6.Tw ...
- DRBD分布式块设备复制
一. DRBD介绍 1.1.数据镜像软件DRBD介绍分布式块设备复制(Distributed Relicated Block Deivce,DRBD),是一种基于软件.基于网络的块复制存储解决方案,主 ...
- 第三篇:Spark SQL Catalyst源码分析之Analyzer
/** Spark SQL源码分析系列文章*/ 前面几篇文章讲解了Spark SQL的核心执行流程和Spark SQL的Catalyst框架的Sql Parser是怎样接受用户输入sql,经过解析生成 ...
- PAT1076. Forwards on Weibo (30)
使用DFS出现超时,改成bfs DFS #include <iostream> #include <vector> #include <set> using nam ...
- U盘安装OS
1. 老毛桃 2. 大白菜 3.
- linux find命令使用(转)
常用命令 find (目录) [-type d | f] (文件夹 | 文件) -name (名称,可使用正则表达式) find /root -name "*core&q ...
- wireshark捕获到的TCP包图示
wireshark抓到的包与对应的协议层如下图所示: wireshark捕获到的TCP包中的每个字段如下图所示:
- vue-cli 脚手架项目简介(一) - package.json
vue-cli是用来生成 vue项目的命令行工具,它的使用方法是这样的: vue init <template-name> <project-name>第二个参数 templa ...
- memcache笔记
服务端: 通过printf配合nc向memcached中写入数据[root@yz6245 ~]# printf "set key1 0 0 6\r\noldboy\r\n" |nc ...
- WCF基础:绑定(二)
在WCF的绑定体系中,经常会碰到ICommunicationObject接口,无论是IChannel接口还是IChannelListener/IChannelFactory接口都继承了ICommuni ...