2014年5月13日星期二

在任意地方,得到socket的信息

用java写servlet时,随时我们都可以得到HttpServletRequest,HttpServletResponse以及session,那么在datasnap里面呢,比如我在服务里面知道当前是哪个ip在call我的服务
跟踪代码,发现session是可以得到,但是比如我打算枚举session的内容,缺没提供方法了。于是,我使用RTTI的盗窃法子,读取了私有变量
procedure RttiGetPrivateValue(AClass: TClass; AInstance: TObject; FieldName: string; var Value: TObject);
var
  ref:  TRttiContext;
  typ: TRttiType;
  mthd: TRttiMethod;
  fld: TRttiField;
begin
  typ := ref.GetType(AClass);
  fld := typ.GetField(FieldName); //can get private value
  Value := fld.GetValue(AInstance).AsObject;
end;

procedure EnumSessionData(const Session: TDSSession);
var
  SessionData: TDSSessionDictionaryData;
  MetaData: TDictionary;
  MetaObjects: TDictionary;
  Key: string;
begin
  RttiGetPrivateValue(TDSSession, Session, 'FSessionData', TObject(SessionData));
  RttiGetPrivateValue(TDSSessionDictionaryData, SessionData, 'FMetaData', TObject(MetaData));
  RttiGetPrivateValue(TDSSessionDictionaryData, SessionData, 'FMetaObjects', TObject(MetaObjects));
  for Key in MetaData.Keys do
    log.Debug('Sesion[%s] = %s', [Key, MetaData[Key]]);
  for Key in MetaObjects.Keys do
    log.Debug('SesionObjects[%s] = %s', [Key, MetaObjects[Key].ClassName]);
end;

function TdmServer.DSServerTrace(TraceInfo: TDBXTraceInfo): CBRType;
var
  Session: TDSSession;
begin
  log.TrackMethod('TdmServer.DSServerTrace');
  Session := TDSSessionManager.GetThreadSession;
  if Session.ObjectCreator <> nil then
    log.Debug(Session.ObjectCreator.ClassName);
  log.Debug(TraceInfo.Message);
  EnumSessionData(Session); //在这里试着枚举读取一下
  Result := cbrUSEDEF;
end;

可以看到,Session里面对于Tcp,默认的只有两个变量,
Sesion[remoteip] = 192.168.101.11
Sesion[communicationprotocol] = tcp/ip
这里的remoteip就是调用者ip了,但是如果想到得到sessioin对应的tunnel的具体socket信息,缺是不能了。 这两个值是在 TDSTCPServerTransport.DoOnConnect里放入的,代码如下
procedure TDSTCPServerTransport.DoOnConnect(AContext: IIPContext);
var
  IndyChannel: TDBXChannel;
  FilterChannel: TDBXFilterSocketChannel;
  Event: TDSTCPConnectEventObject;
begin
  FilterChannel := TDBXFilterSocketChannel.Create(Filters);

  IndyChannel := CreateTcpChannel(AContext);

{$IFNDEF POSIX}
  if CoInitFlags = -1 then
    CoInitializeEx(nil, COINIT_MULTITHREADED)
  else
    CoInitializeEx(nil, CoInitFlags);
{$ENDIF}
  IndyChannel.Open;

  // set the delegate
  FilterChannel.Channel := IndyChannel;

  AContext.Data := FProtocolHandlerFactory.CreateProtocolHandler(FilterChannel);
  if AContext.Data is TDBXJSonServerProtocolHandler then
  begin
    if TDBXJSonServerProtocolHandler(AContext.Data).DSSession = nil then
    begin
      TDBXJSonServerProtocolHandler(AContext.Data).DSSession :=
        TDSSessionManager.Instance.CreateSession(
          function: TDSSession
          begin
            Result := TDSTCPSession.Create(AuthenticationManager);
            Result.PutData('CommunicationProtocol', 'tcp/ip'); //这里放了进去
            Result.PutData('RemoteIP', AContext.Connection.Socket.Binding.PeerIP); //这里放了进去信息
          end,
          ''
        );
    end;
  end;

  if Assigned(FTDSTCPConnectEvent) and (AContext <> nil) and (AContext.Connection <> nil) and
    (FTcpServer <> nil) and FTcpServer.Active then
  begin
    if IndyChannel Is TDSTCPChannel then
    begin
      //enable keep alive, disable it, or leave the OS default setting as it is
      if FKeepAliveEnablement = kaEnabled then
        TDSTCPChannel(IndyChannel).EnableKeepAlive(FKeepAliveTime, FKeepAliveInterval)
      else if FKeepAliveEnablement = kaDisabled then
        TDSTCPChannel(IndyChannel).DisableKeepAlive;

      Event := TDSTCPConnectEventObject.Create(AContext.Connection.GetObject, TDSTCPChannel(IndyChannel));
    end
    else
      Event := TDSTCPConnectEventObject.Create(AContext.Connection.GetObject, nil);

    try
      OnConnect(Event);
    except
    end;
  end;
end;

留意一下key的大小写进去的是RmoteIp,出来却变成了remoteip了,所以默认的Session是不关系大小写的,这是因为TDictionary创建是并没指定TStringComparer.Ordinal。目前DataSnap还不支持自定义TDSSessionData的派生,因为TDSSession.CreateSessionData不是虚的。

2014年5月12日星期一

我写的一个注册服务方法的Unit

先定义一个基础服务方法类
unit uServerMethod;

interface

uses
  System.SysUtils, System.Classes,
  Datasnap.DSProviderDataModuleAdapter,
  Datasnap.DSServer, Datasnap.DSCommonServer, Datasnap.DSReflect;

type
  {$METHODINFO ON}
  TServerMethod = class(TPersistent)
  public
    constructor Create; virtual;
    destructor Destroy; override;
  end;
  {$METHODINFO OFF}

  TServerMethodClass = class of TServerMethod;

procedure RegisterServerMethodClass(AClass: TServerMethodClass);
procedure RegisterServerMethodClasss(AOwner: TComponent; AServer: TDSServer);

implementation

uses
  uLog;

type
  TDSServerClass = class(Datasnap.DSServer.TDSServerClass)
    private
      FServerMethodClass: TServerMethodClass;
    protected
      function GetDSClass: TDSClass; override;
      procedure CreateInstance(const CreateInstanceEventObject: TDSCreateInstanceEventObject); override;
      procedure DestroyInstance(const DestroyInstanceEventObject: TDSDestroyInstanceEventObject); override;
    public
      constructor Create(AOwner: TComponent; AServer: TDSCustomServer; AClass: TServerMethodClass); reintroduce; overload;
      destructor Destroy; override;
    end;

var
  ServerMethodList: TList;
procedure RegisterServerMethodClass(AClass: TServerMethodClass);
begin
  ServerMethodList.Add(AClass);
end;

procedure RegisterServerMethodClasss(AOwner: TComponent; AServer: TDSServer);
var
  I: Integer;
begin
  for I := 0 to ServerMethodList.Count - 1 do
  begin
    TDSServerClass.Create(AOwner, AServer, ServerMethodList[I]);
  end;
end;

{ TDSServerClass }

constructor TDSServerClass.Create(AOwner: TComponent; AServer: TDSCustomServer;
  AClass: TServerMethodClass);
begin
  inherited Create(AOwner);
  FServerMethodClass := AClass;
  Self.Server := AServer;
end;

procedure TDSServerClass.CreateInstance(
  const CreateInstanceEventObject: TDSCreateInstanceEventObject);
var
  AObject: TServerMethod;
begin
  inherited;
  if CreateInstanceEventObject.ServerClassInstance = nil then
  begin
    CreateInstanceEventObject.ServerClassInstance := FServerMethodClass.Create; //不要让TDSClass.CreateInstance去创建
  end;
end;

destructor TDSServerClass.Destroy;
begin
  inherited;
end;

procedure TDSServerClass.DestroyInstance(
  const DestroyInstanceEventObject: TDSDestroyInstanceEventObject);
begin
  inherited;

end;

function TDSServerClass.GetDSClass: TDSClass;
begin
  Result := TDSClass.Create(FServerMethodClass, False);
end;

{ TServerMethod }

constructor TServerMethod.Create;
begin
  log.Debug('%s.Create', [ClassName]);
end;

destructor TServerMethod.Destroy;
begin
  log.Debug('%s.Destroy', [ClassName]);
  inherited;
end;

initialization
  ServerMethodList := TList.Create;
finalization
  FreeAndNil(ServerMethodList);

end.
然后就可以开始继承实现做应用了,写个例子
unit sSample;

interface

uses
  uServerMethod;

type
  TSmSample = class(TServerMethod)
  public
    function EchoString(Value: string): string;
    function ReverseString(Value: string): string;
  end;

implementation

{ TSmSample }

uses
  System.StrUtils;

function TSmSample.EchoString(Value: string): string;
begin
  Result := Value;
end;

function TSmSample.ReverseString(Value: string): string;
begin
  Result := System.StrUtils.ReverseString(Value);
end;

initialization
  RegisterServerMethodClass(TSmSample);

finalization

end.
以后关注服务方法实现就可以了。

TObject.Create is not a virtual method

DataSnap在创建服务方法类的时候,如果从TPersistent继承写服务方法,不写DSServerClass的DSServerClass1CreateInstance事件,是会有问题

比如我定义一个服务方法
 
  {$METHODINFO ON}
  TServerMethod = class(TPersistent)
  public
    constructor Create; virtual;
    destructor Destroy; override;
  end;
  {$METHODINFO OFF}

由于TObject.Create不是虚方法,在使用
var
  ClassRef: TTPersistentClass;
  M: TServerMethod;
begin
  ClassRef := TServerMethod;
  M := ClassRef.Create; //这里有问题,这个的Create,不会call到TServerMethod.Create,只会call TObject.Create,也就是啥也没干
end;
请看代码
function TDSServerConnectionHandler.CreateInstance(const ServerClass: TDSCustomServerClass; const DsClass: TDSClass): TObject;
var
  Instance: TObject;
begin
  if ServerClass <> nil then
  begin
    FCreateInstanceEventObject.ServerClassInstance := nil;
    ServerClass.CreateInstance(FCreateInstanceEventObject); //如果没有实现DSServerClass1CreateInstanc事件,这里的FCreateInstanceEventObject.ServerClassInstance会是nil
    Instance := FCreateInstanceEventObject.ServerClassInstance;
    if Instance <> nil then
      Exit(Instance);
  end;
  Result := DsClass.CreateInstance;//所以会走到这里
end;

function TDSClass.CreateInstance: TObject;
var
  AdapteeInstance: TObject;
  Component: TComponent;
begin
  if Assigned(FClassRef) then
  begin
    if Assigned(FAdapteeClass) then
    begin
      AdapteeInstance := FAdapteeClass.CreateInstance;
      Result := TDSAdapterClassType(FClassRef).Create(AdapteeInstance);
    end
    else
    begin
      if FClassRef.InheritsFrom(TComponent) then
      begin
        // Allows Forms and DataModules to read in the components
        // they contain.
        //
        Component := FClassRef.NewInstance as TComponent;
        Component.Create(nil);
        Result := Component;
      end
      else
        Result := FClassRef.Create//然后再走到这里,FClassRef是一个TTPersistentClass,它的Create,不会触发TServerMethod.Create
    end;
  end
  else
    Result := nil;
end;

郁闷吧,TObjec.Create不是虚方法多少年前都已经讨论过,自然有它的道理。 要解决这个问题,用上面对待TComponent方法来做可以,这样的方法其实在TApplication.CreateForm里面也用到了。
        //Result := FClassRef.Create//然后再走到这里,FClassRef是一个TTPersistentClass,它的Create,不会触发TServerMethod.Create
        //修改为,先定义一个TServerMethod的变量,再NewInstance再Create,再赋值。        

2014年5月9日星期五

FireDac的的初步2

FireDac和UniDac一样了,也支持所谓的连接池,但是限制比较多。

要支持连接池,必须让FDManager连管理,也就是,必须:1,FDConnectionDefs.ini在定义一个连接,2,必须FDConnection的Params必须是空的。第二个条件比较容易理解,因为要是不同的FDConnection指定了不同的连接参数,那么连接池里面的连接属性肯定需要不一样才行。第一个条件就比较恶心了,莫非都得需要这个配置文件才可??岂不是App都要带一个这个FDConnectionDefs.ini??

仔细看了这里

发现可以定义FireDAC supports 3 connection definition kinds:的Private类型也可以只是Pool。

代码如下了
procedure TForm1.FormCreate(Sender: TObject);
var
  oParams: TStrings;
begin
  with FDManager do
  begin
    oParams := TStringList.Create;
    oParams.Add('Database=xxx.xxx.xxx.xxx:1521/orac');
    oParams.Add('Pooled=True');
    oParams.Add('DriverID=Ora');
    oParams.Add('User_Name=username');
    oParams.Add('Password=password');
    oParams.Add('OSAuthent=No');
    oParams.Add('POOL_MaximumItems=1'); //搞一个测试看看
    FDManager.AddConnectionDef('Ora_Pooled', 'Ora', oParams);
    FreeAndNil(oParams);
  end;
end;
  
这样就可以定义一个 Private的连接了
 
另外,看了文档,也明白了,连接定义FDConnectionDefs.ini的名字可以自己起,然后设置上 
FDManagerConnectionDefFileName就可以,这样还灵活一点点。 

这个【Private】的意思,没看明白,因为就算【Persistent】也肯定是不能跨Exe来Pool的。
 

连接池的使用,直接定义一个conn,然后设置
procedure TForm1.Button1Click(Sender: TObject);
var
  conn: TFDCustomConnection;
begin
  conn := TFDCustomConnection.Create(nil);
  conn.ConnectionDefName := 'Ora_Pooled';
  FDQuery1.Connection := conn;
  FDQuery1.Active := True;

  conn := TFDCustomConnection.Create(nil);
  conn.ConnectionDefName := 'Ora_Pooled';
  FDQuery2.Connection := conn;
  FDQuery2.Active := True;
end;
启动了pool后,conn的close也不是真的close,只是还给pool。 当超过
POOL_MaximumItems定义的值时,conn的发起连接时就会报异常了。
监视oracle会发现有两个连接连接上了。
另外连接池和
  FDManager.AcquireConnection
  FDManager.ReleaseConnection
一分钱关系都没有

 

FireDAC的初步1

把AnyDac买进来后,Embarcadero重新起了个名字叫FireDAC,以前用RO时挂过AnyDAC,当时觉得deploy程序的时候,配置太繁琐了,也一直没用,因为有UniDAC的存在,AnyDac的确是既生瑜何生亮。选择UniDac的另一个原因是,UniDac对Oracle的Direct连接,不需要套Oracle客户端dll,deploy时真的时方便又快捷。

UniDac是要钱的,打算再次摸一摸FireDac,做个笔记,记录一下。另外老被吹嘘的dbExpress也算是寿终正寝了。目前FireDac的Driver是不支持DataSnap的,估计dbExpress还会保留一段时间。

基本上,我都是用Oracle,所以比较关心without oracle client的数据库连接是否能做到。

测试了几下,FireDac连接Oracle,在没有Oracle Client的情况下,是可以连接上的。方式是

前提:需要文件

    oci.dll
    oraocci11.dll
    oraociei11.dll
    orasql11.dll

这几个文件,可以放在exe同一个目录下,或者path里面能找到得到的地方。有了这4个文件,就可以连接oracle了

具体参考了这个

我不喜欢tnsname.ora的配置,因为需要外挂一个配置文件。还是Oracle easy connect string方式来的最简单直接,形式如

OraSrv:1521/orcl ,这个就和UniDac的Direct连接时指定(OraSrv:1521:orc)差不多了。

2013年3月9日星期六

想玩孤岛危机3

想玩孤岛危机3,想整个电脑。回想2008年为了玩孤岛危机2,整了个GTX280的24W左右的电脑,2011年才卖6万,又有些犹豫。上上周订购了一台dell的18 R2,7970M的交火,碰上促销,19万2本是捡了个便宜。后来发现就算 7970M 的交火,玩 孤岛危机3 也才45帧,就取消订单了。现在就45帧,岂不是将来的孤岛危机4 啥的根本没法玩了。 还是等等到6月看haswell的cpu出来再说吧。

2012年12月8日星期六

在GPT分区上安装windows7

要在GPT分区上安装windows7,必须要是64位版本的的才行,32位不支持gpt分区。另外,要注意的是,要在gpt分区上安装系统,得主板支持UEFI才行,启动得用UEFI BIOS启动才行:比如从光驱驱动就得UEFI xxxx CDROM这样的才行。