• RealThinClient LinkedObjects Demo解析


    这个Demo源码实现比较怪,有点拗脑,原因估是作者想把控件的使用做得简单,而封装太多。

    这里说是解析,其实是粗析,俺没有耐心每个实现点都查实清楚,看源码一般也就连读带猜的。

    这个Demo表达出的意义,在于在HTTP访问方式下,很方便的实现了客户端和服务端相互的主动通讯能力,这在需要实时消息交互,控制交互,数据互传上非常有意义,非常给力。

    一、Demo工作过程

    1. 实现功能:实现了上传文件到HTTP服务端的能力,大文件上传成功率极高,并且始终保持非常少的内存占用量。

    2. 工作过程:客户端和服务端的TRtcLinkedModule控件需建立通讯连接,在Demo中有两种方式,客户端先创建TRtcLinkedModule对象和服务端先创建TRtcLinkedModule对象,但无论哪一端先创建,都会引发对端也创建对象,从而使两对象的通讯连接建立。完成建立后,服务端把文件存储路径推送给客户端,客户端选择文件后,调用服务端Upload处理流程(这儿只能说是处理流程,而不是函数,因为TRtcLinkedModule对象的远程调用总是只触发对端的OnCallMethod事件,通过在Param中使用字符串标识,如“Upload”告诉服务端使用哪一个处理流程)开始上传,服务端在Upload中先判断是否文件已存在,不存在的话,直接调用客户端的“Get”流程让其上传数据,且每次“Get”调用只要求客户端发送10K数据,客户端准备好数据后调用服务端的“Put”把数据送到服务端并写入文件,“Put”过程又再调用“Get”,如此往复,直至完成全部数据上传。

    二、实现原理和关键函数

    1. TRtcLinkedModule控件

       (1)此控件可放置在Form、DataModule、Frame这类容器上,一个容器只能有一个该控件!放置了该控件的模块即成为通讯模块,可以在通讯需要时由远端发起实例创建请求而创建(除了放控件外,当然还有几行代码的工作要做,下面讲述)。

       (2)控件的字符串属性RemoteClass设置为远端的模块名字,这样当要远端创建Link模块进行通讯时,RTC会把此名字作为标识发送创建请求到远端,远端则根据名字创建正确的模块。

       (3)成员函数CallMethod(const xMethodName: string)直接触发对端的OnCallMethod事件,事件中根据xMethodName确定处理方式,看起来一般用于数据块或复杂控制上。

       (4)成员函数SetProp(const xPropName: string; xNow: boolean)、SetEvent(const xEventName: string)直接触发对端的OnSetProp和OnSetEvent事件,用于简单的短消息交互或控制交互。

       (5)成员函数Broadcast(const xChannel, xMethodName: string)在本地模块内群播消息,xChannel为群播管道,管道由成员函数Subscribe(const xChannel: String)建立。

       (6)控件使用相当简单,以上属性设置后就完成,不用管如何和http通信控件啊或ClientModule等远调控件进行连接,扔上就可用了(这也是让人比较疑惑的地方,后面有解析它是如何和http通信控件关联的)。

    2. 函数RegisterRtcObjectConstructor(const xClassName:String; xProc:TRtcObjectConstructor):

       (1)必需在initialization块执行,必需和UnregisterRtcObjectConstructor配对使用
       (2)这个函数用于指定一个能被远端调用的构造函数,用于能让远端在需要时创建TRtcLinkedModule对象及模块
       (3)函数的xClassName在TRtcLinkedModule控件中的RemoteClass中指定,xProc为一个procedure MakeObject(Sender:TObject; Param:TRtcObjectCall);类型的静态函数,这个函数就是为什么在需要时就能创建远端Link模块的关键了,创造需求到来时,根据RegisterRtcObjectConstructor函数的注册信息,找到xProc函数调用,OK,Link模块对象生成了,原理很简单吧。
       (4)另一个替代方案为可以在TRtcClientModule或TRtcServerModule控件中的OnObjectCreate事件中进行创建工作,但这需要两端使用此Module控件进行通讯。

    函数意义:远端发送创建LinkObject通迅对象命令,本地端根据远端发来的xClassName字串在ObjectManager中找到注册的指定构造函数并调用。所以程序需要使用ActivateObjectManager创建好对象管理器。

    3. 函数ActivateObjectManager(xCreate:boolean=True),TRtcDataServer和TRtcClientModule成员函数。

      (1)在使用TRtcLinkedModule控件进行通讯前必需调用该函数,否则不能和远端通信,也不能正常发起远端模块创建请求。

      (2)RtcClientModule1.ObjectLinks为“ol_Manual”必需手动调用,设置为“ol_AutoClient”或“ol_AutoBoth”则RTC会在后台自动调用。

    忍不住插句对于远程函数调用架构的一点看法:

    RemObject套件用的是看起来很符合常规理解的函数调用,让你感觉不到这是远程函数,为实现这样一个使用感受,RO用了比较复杂的技术来实现,不可否认,这样相当有含金量,不是谁都可以写得出来的,这种技术曾如此让我赞服并学习。而RTC的远程调用全部用字符串标识方式,如此的简单,实现起来不需要复杂的技术,大多有点经验的程序员基于TCP之类底层都可以做出来,而但多年后的现在,我才发现这种简单的美和高效,字符串可以描述从简单到极其复杂的构造,网页啊,xml啊这不都是这样描述的么,因而其灵活度可以说没有羁绊,要实现什么能实现什么如何实现只在乎用者一心。RTC的作者在编程思想境界上一定已是十分的修为深厚了。

    三、奇怪的地方

        按道理,TRtcLinkedModule控件要能进行通讯,必定要有设置通讯方式的地方,但找完TRtcLinkedModule的属性和Demo的源码,都没有发现有设置的地方,那它是怎么完成通讯的呢?

       先看看TRtcLinkedModule的构造函数里都干了什么:

     1 constructor TRtcLinkedModule.Create(AOwner: TComponent);
    2 begin
    3 inherited Create(AOwner);
    4 if not (csDesigning in ComponentState) then
    5 begin
    6 // This will only work if there is an active Object Manager for the current thread
    7 FParam:=TRtcObjectCall.Create(GetRtcObjectManager);
    8 if assigned(AOwner) then
    9 FOLink:=TRtcBasicObjectLink.Create(AOwner,FParam.Manager)
    10 else // If no Owner, we are the Object container
    11 FOLink:=TRtcBasicObjectLink.Create(self,FParam.Manager);

    看到了GetRtcObjectManager,这是个全局函数,根据线程ID获取对象管理器,ObjectManager估计是个全局的、唯一的单例模式的对象,懒得看代码了,猜的,哈哈。

    ObjectManager估计是个LinkObject的管理器,用于Link控件发送的请求到来时找到正确的模块及正确的Link控件,然后调用对应的函数,现在的疑问是,ObjectManager怎么知道把请求发送到对应的Form和Link去呢?上面代码中FOLink:=TRtcBasicObjectLink.Create(AOwner,FParam.Manager),看到传入AOwner了!还有Manager,干啥呢?继续跟下去看看TRtcObjectLink.Create是什么:

     1 constructor TRtcObjectLink.Create(xOwner:TObject;xManager:TRtcObjectManager);
    2 begin
    3 inherited Create;
    4 if xOwner=nil then
    5 raise ERtcObjectLinks.Create('TRtcObjectLink.Create called with xOwner=nil');
    6 if xManager=nil then
    7 raise ERtcObjectLinks.Create('TRtcObjectLink.Create called with xManager=nil');
    8 FSubs:=nil;
    9 FOwner:=xOwner;
    10 FManager:=xManager;
    11 if FManager.CreatingObjectID<>0 then // remote constructor
    12 begin
    13 FOID:=FManager.CreatingObjectID;
    14 FManager.CreatingObjectID:=0;
    15 FCreator:=False;
    16 end
    17 else
    18 begin
    19 FOID:=FManager.GetNextObjectID;
    20 FCreator:=True;
    21 end;
    22 FRemoteDestroyed:=False;
    23 FManager.AddObject(FOID,self,FOwner);
    24 end;

    最后一句:FManager.AddObject(FOID,self,FOwner);很明显了,这儿TRtcObjectLink的对象标识FOID和自身对象指针Self及所在的窗口FOwner被传入,三个的对应关系都有了,ObjectManager要找到正确的模块那是很容易的事。而TRtcObjectLink对象内嵌于TRtcLinkedModule控件对象中,是组合关系,具体函数和事件调用是TRtcObjectLink的实现。

    现在还有个问题是,Link对象是怎么得到通讯能力的?

    注意到,RtcClientModule1.ActivateObjectManager(True);这个调用,RtcClientModule1是连接到TRtcHttpClient对象的,因此具备http通讯能力,再看看RtcClientModule1和ObjectManager是什么关系:

     1 procedure TRtcClientModule.ActivateObjectManager(xCreate:boolean=True);
    2 begin
    3 if FRelease then Exit;
    4
    5 if ObjectLinks=ol_None then
    6 raise ERtcObjectLinks.Create('ActivateObjectManager: ObjectLinks = ol_None')
    7 else if not assigned(FObjectManager) then
    8 if xCreate then
    9 begin
    10 FObjectManager:=TRtcClientObjectManager.Create(False);
    11 FObjectManager.BroadcastGroup:=String(FObjManSesName);
    12 FObjectManager.OnDataReady:=DoObjectManagerDataReady;
    13 end;
    14
    15 if not assigned(FObjectManager) then
    16 raise ERtcObjectLinks.Create('ActivateObjectManager failed')
    17 else
    18 begin
    19 SetRtcObjectManager(FObjectManager);
    20 FObjectManager.ExecuteBroadcast(nil);
    21 end;
    22 end;

    噢,原来这家伙发现没有ObjectManager的话就创建一个并持有,并且用SetRtcObjectManager送到一个全局对象fObjManagers中,这个对象是个列表,而GetRtcObjectManager正是从这个fObjManagers中获取,现在真相大白了,原来就是TRtcHttpClient控件收到请求转给TRtcClientModule对象,TRtcClientModule对象的私有成员FObjectManager保存着ObjectManager的对象引用,ObjectManager对象根据保存的Owner、Link对象关系找到正确的模块,调用Link对象相关函数。嗯,不过具体代码没有细看,以上真相有较大猜测成份,不过我相信道理上应对的,实质性实现上,请看到这篇文章的有心网友指出,批评指正,共同进步。

  • 相关阅读:
    Python学习笔记(四)多进程的使用
    Python学习笔记(三)多线程的使用
    windows无法安装msi文件
    标签传播算法
    信息论基础
    模块度Q
    HTTPS开发(SSL--用Tomcat服务器配置https双向认证)
    oracle 优化
    eclipse 界面开发--windowbuilder
    vba 读取数据库
  • 原文地址:https://www.cnblogs.com/IceAir/p/2332597.html
Copyright © 2020-2023  润新知