datasnap的初步

轉自:http://www.cnblogs.com/china1/p/3380333.html


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(20)).Free;

  sleep(1000);

  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屬性,可理解爲ClientIdClientId必須是唯一的,相同的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只能做到服務器向客戶端推送數據,單向的。客戶端要向服務器送數據,走TDSAdminClientNotifyCallback方法,也就是隻有經過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 >0)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的消息,這就讓ConnectClientChannelrepeat循環也會退出(data=nil),從而關閉最開始連接。

另外, TDSClientCallbackChannelManager在界面上找不到輸入認證信息的地方,比如DSAuthPassword, DSAuUser等,其實TDSClientCallbackChannelManager也好SQLConnection也好,他們都是個載體罷了,真正在後面起作用的,是TDBXProperties。監視一下DSServerOnConnect裏的DSConnectEventObject.ConnectProperties,我們就能知道TDSClientCallbackChannelManagerusername, password,其實是SQLConnectionDSAuthPasswordDSAuUser

TDBXProperties裏的鍵值對爲

DSAuthenticationUser=

DSAuthenticationPassword=

最後,TDSClientCallbackChannelManager並沒有Filters的屬性,這個其實現在的客戶端,就算使用SQLConnection時也不必設置Filters了,我們只要別忘記在客戶端也TTransportFilterFactory.RegisterFilter一下要用的Filter即可。

datasnap的初步 序列化自己寫的類

今天在網上找到了一個marshallunmarshall的例子,將自己的定義的類,序列號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 :=29;

      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 =29);

    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

這個更細,每次ServerMethodCall,都將創建和銷燬一PersistentClass的實例。由於創建銷燬比較耗資源,可以操作TDSServerClassOnCreateInstanceOnDestroyInstance事件,在這兩個事件裏面做緩存池。代碼如下

複製代碼
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;

這樣定義後,就算不寫DSAuthenticationManager1UserAuthorizeTDSAuthenticationManager也會自動幫你檢查該角色是否有權利調用該ServerMethodRTTI估計是學JavaAnnotation才增加了TCustomAttribute

 2.客戶端

客戶端很簡單了,設置SQLConnectionDSAuthUserDSAuthPassword就行了。

datasnap的初步 對象的銷燬

TServerMethods1Client繼承自TDSAdminClient,這個類的構造函數

constructor Create(ADBXConnection: TDBXConnection); overload; 

後面的AInstanceOwner參數,挺重要,理解這個,對於避免內存泄漏有很大好處。

 默認情況下,我們使用Create來創建ServerMethodClient,也就AInstanceOwnertrue了,也就是所有進入 TServerMethods1Client類方法的參數,都被ServerMethodClient給來釋放。我覺得EMBT推薦使用 AInstanceOwner=True

 DATASNP如何釋放內存,請看代碼

客戶端 

複製代碼
procedureTDBXCommand.CommandExecuting;

begin

  ifAssigned(FFreeOnCloseList)then  

    FreeOnExecuteObjects;//這裏釋放

  Open;

  CloseReader;

  if(FParameters <>nil)and(FParameters.Count >0)then

  begin

    ifnotFPreparedthen

      Prepare;

    SetParameters;

  end;

end;
複製代碼

也就是說,對每個DBXCommand,每次執行前都會清理上一回留下的垃圾。當然最後的垃圾肯定要等到DBXCommand.Close時纔去清理了。

對於function(a: TA, out b: TB): TA這樣的調用 

 AInstanceOwnertrue時,a, b, 以及返回值result我們都不用去自己Free。尤其要注意入口參數a,可能進去執行後立刻被Free(需要被Marshal的類),也可能是等到下次Call時被Free(比如TStream)

 反之,則都需要自己去free。但是TDBXCallback,是個例外,就算AInstanceOwnerFalse,也不能自己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的初步 關於TDSTCPServerTransportFilters 

TDSTCPServerTransportFilter屬性,可以對傳遞的數據進行加密,壓縮,再修改等,有點注入的概念。默認情況下,Datasnap自帶的ZLIB, PC1RSA三個Filter。測試了一下,RSA只對KEY加密,PC1纔對內容加密,ZLIB來做壓縮,ZLIB壓縮實在不咋的。並且,Filter的順序,是依次執行的。我現在打算實現,服務器的一個Log功能,記錄下來進入的數據,出去的數據,要求記錄下來的數據是明文。

 TTransportFilterProcessInputProcessOutput光看名字比較費解,可以這麼理解ProcessInput爲編碼,ProcessOutput可以理解爲解碼。

首先給DSTCPServerTransport1Fitlers都加上默認的3Filter

上一個完整的代碼

複製代碼
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實現了上面的功能,

數據進入服務器時,DataSnapReader讀出時按順序經過Filter進行解碼,最後的Filter,也就是這裏的TLogTailFilterProcessOutput出來的肯定應該是明文了,記錄下來。

數據出服務器時, DataSnapWriter寫數據時,也按順序經過Filter進行編碼,剛開始的肯定是明文的,也就是TLogHeadFilterProcessInput了,記錄下來。

要使用這個unit,只要在ServerContainerUnit1單一的OnCreate裏面寫入即可。如下

複製代碼
procedure TServerContainer1.DataModuleCreate(Sender: TObject);

begin

  AddLogFilter(DSTCPServerTransport1.Filters); 

end; 
複製代碼

最後,上個圖,看看client和服務器之間的通訊是怎樣的。

爲方面看,我分開了,第一次是connect,然後二次調用了EchoString。可以看出一次servermethod,有3個來回的交流(一個prepare, 一個execute,一個command_closepreparecommand_close並不是每次必須的,這裏因爲我是每次都創建新的TServerMethods1Client),並且交流的數據都是JSON的整列。這裏也打印出了編碼解碼前數據長度,以及編碼解碼後的數據長度,如果要測試ZLIB的壓縮效果,可以參考。

或許要說DSServerOnTrace事件也可以玩,但是 OnTrace只能記錄ClientServer的數據,對出去的數據TRACE不到的,很遺憾。

最後,有一些其他的現成的開源Filter可用,尤其是壓縮的,去http://code.google.com/p/dsfc/

datasnap的初步 直接返會自定義類

前面我說datasnap不支持自定義類型是錯誤的。其實datasnap一旦發現是自定義類型,就會自動用jsonmarshall了,今天的測試代碼如下。

服務器端

複製代碼
functionTServerMethods1.GetPerson: TPerson;

begin

  Result := TPerson.Create;

  Result.FirstName :='zyz';

  Result.LastName :='Jacky';

  Result.Age :=21;

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[0].Value.IsNullthen

  begin

    FUnMarshal := TDBXClientCommand(FGetPersonCommand.Parameters[0].ConnectionHandler).GetJSONUnMarshaler;

    try

      Result := TPerson(FUnMarshal.UnMarshal(FGetPersonCommand.Parameters[0].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使用的默認爲trueUnMarshalCreateObject產生的類,讓Datasnap自己去釋放了,所以p.free要注視掉。釋放的時機有兩個:

1。下一次調用到來

2DBXCommand.Close

datasnap的初步 獲得客戶端的信息

記得datasnap 2009時,要得到客戶端信息,非官方的方法,要去搞什麼DSConnectEventObject.ChannelInfo.Id,弄成 TIdTCPConnectionxe2就好得多了。

仍然是在DSServerOnConnect 事件裏,

DSConnectEventObject.ChannelInfo.ClientInfo就是客戶端的信息。能得到啥? 

看代碼 

複製代碼
TDBXClientInfo =record

    IpAddress:String;

    ClientPort:String;

    Protocol:String;

    AppName:String;

  end;
複製代碼

也就是能取得客戶端ip,端口,連接協議,不過AppName這玩意兒一直是空的。

執行到 DSServerOnConnect的事件裏,其實socket已經完全連上了,client已經調用了serverconnect方法了,在這個方法裏觸發的OnConnect。所以DSServerOnConnect其實並不是真的socketOnConnect 

datasnap的初步 Session的管理 

Datasnapsession管理是強制的,沒有選項能說不要。

管理靠一單例TDSSessionManager來管理。對於目前說到TDSTCPServerTransport,建立的的SessionTDSTCPSession,它是TDSSession的子類。

Session在開始連接後,就創建了,再連接斷開後消亡。

複製代碼
TDSSession =class

 private

   FStartDateTime: TDateTime;  /// creation timestamp

   FDuration:Integer;         /// in miliseconds, 0 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可以用存儲了很多東西 。用得多的是FMetaDataFMetaObjects

對於字符串,PutData放進去,GetData取出來;對於ObjectPutObject放進去,GetObject取出來。

使用方法爲

TDSSessionManager.GetThreadSession.PutData('userid', userId);

userId := TDSSessionManager.GetThreadSession.GetData('userid');

另外,放入FMetaObjectsObjectSessionFree時,會自動幫忙Free,所以不必自己去Free的。

關於Session的超時, 

這裏自然就想到了TDSTCPServerTransportKeepAliveIntervalKeepAliveTime屬性,這兩個屬性,其實和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 >=5)then

  begin

    ka.onoff :=1;

    ka.keepalivetime := ATimeMS;

    ka.keepaliveinterval := AInterval;

    WSAIoctl(ASocket, SIO_KEEPALIVE_VALS, @ka, SizeOf(ka),nil,0, @Bytes,nil,nil);

  endelsebegin

    SetSocketOption(ASocket, Id_SOL_SOCKET, Id_SO_KEEPALIVE, iif(AEnabled,1,0));

  end;

end;
複製代碼

裏,其實就是簡單設置了一下socket fd的屬性,所以說TDSSessionManager毛關係都沒有。

另外, KeepAliveTime默認值爲300000,也就是300秒,KeepAliveInterval默認值爲100,這是啥意思呢。KeepAliveTimesockfd最後一次通訊後,等待了的時間,如果300秒內沒通訊,socket棧就自己開始發送心跳探測了,如果每次都沒回答,就每隔KeepAliveInterval毫秒問一次。至於問多少次認爲是網絡斷開了,根據Windows OS來定的,windows 2000, 20035次,vista以後問10次。也就是說,根據TDSTCPServerTransport的默認設定,網絡斷了,在win7上,要300+0.1*10,也即是301秒才知道網絡斷了。

OS的系統設定更長,沒數據通訊後2小時纔開始探測,每隔1秒探測一回。

SIO_KEEPALIVE_VALSWindowsOS獨有的,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);
複製代碼

這樣就可以了,有多少事件都可以插入監聽。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章