使用Delphi进行透明远程处理最简单的解决方案是什么?

时间:2010-10-12 16:54:21

标签: delphi design-patterns remoting rpc rtti

我有一个两层Delphi for Win32应用程序,在一个神对象中实现了很多业务逻辑,我想将其外包到一个单独的服务中。多个客户端应通过TCP / IP telnet风格协议访问此单独服务。

如何使转换变得最简单?

准确地说,我想保持这种简单性:我想只定义一次所有功能。例如,如果我想在我的应用程序中添加pin代码登录功能,我只需要定义

 function Login(Username: string; PinCode: integer): boolean;

在服务器上的某个对象中运行,然后我可以在没有任何额外工作的情况下从客户端使用它。

在最坏的情况下,我必须实现三个功能而不是一个。首先,函数体本身在服务器上,第二,unmarshaller从网络接收文本行,解压缩并检查有效性:

 procedure HandleCommand(Cmd: string; Params: array of string);
 begin
   ...
   if SameText(Cmd, 'Login') then begin
     CheckParamCount(Params, 2);
     ServerObject.Login(
       Params[0],
       StrToInt(Params[1])
     );
   end;
 end;

第三,marshaller在被客户端调用时打包params并将它们发送到服务器:

function TServerConnection.Login(Username: string; PinCode: integer): boolean;
begin
  Result := StrToBool(ServerCall('Login '+Escape(Username)+' '+IntToStr(PinCode)));
end;

显然,我不想要这个。

到目前为止,我已经设法摆脱了unmarshaller。使用Delphi RTTI,我写了一个通用的unmarshaller,它按名称查找已发布的方法,检查params并调用它。

所以现在我可以将已发布的方法添加到服务器对象中,我可以从telnet调用它:

 function Login(Username: string; PinCode: integer): boolean;

 > login john_locke
 Missing parameter 2 (PinCode: integer)!

但是我怎么做关于编写编组的人呢?我无法动态获取服务器功能列表并向客户端对象添加功能。我可以保留一些动态的伪函数集合,但这会让我的客户端调用丑陋:

ServerConnection.Call('Login', [Username, Password]);

另外,这会破坏类型安全性,因为每个参数都作为变量传递。如果可能的话,我想保持编译时类型安全。

也许客户端代码自动生成?我可以在我的服务器中写“GetFunctionList()”和“GetFunctionPrototype(Name:string)”:

> GetFunctionList
Login
Logout
IsLoggedIn

> GetFunctionPrototype Login
function Login(Username: string; PinCode: integer): boolean;

因此,每次我需要更新客户端时,我只需从服务器重新查询所有函数原型并自动为它们生成marshaller代码。但这会混合编译和执行:我必须首先编译服务器,然后启动它并查询其功能,然后构建客户端编组器并在此之后重新编译客户端。复杂!

另一个选择是编写一个通用的marshaller函数,然后从所有客户端原型函数中调用它:

procedure TServerConnection.GenericMarshaller(); assembler;
asm
  //finds the RTTI for the caller function, unwinds stack, pops out caller params,
  //packs them according to RTTI and sends to the server.
  //receives the result, pushes it to stack according to RTTI, quits
  //oh god
end;

function TServerConnection.Login(Username: string; PinCode: integer): boolean; assembler;
asm
  call GenericMarshaller
end;

这节省了我每次编写手动包装(错误的可能性更小),但仍然需要我手动将服务器功能原型复制到客户端对象中。另外,写这个通用编组可能会是一个活生生的地狱。

然后可以选择使用RPC,但我不喜欢它,因为我需要重新定义IDL中的所有函数。 Delphi的IDL编辑很糟糕。对于OLE接口,Delphi强制生成“safecall”函数,这些函数也很糟糕。除了检查对RPC类的EVERY SINGLE函数调用之外,没有办法实现自动检测断开和自动恢复连接功能:

function TServerWrapper.Login(Username: string; PinCode: integer): boolean;
begin
  try
    RealObject.Login(Username, Pincode);
  except
    on E: EOleException do
      if IndicatesDisconnect(E) then
        Disconnect;
      Reconnect;
      RealObject.Login(Username, Pincode);
  end;
end;

我们回到了几个功能而不是一个。

那么,你们会建议什么?我缺少的其他选择是什么?在Delphi中是否有常见的模式或远程处理的现成解决方案?

4 个答案:

答案 0 :(得分:7)

就个人而言,我认为您不应该构建基于透明远程处理的分布式系统。对于健壮且高效的客户端 - 服务器交互,过程级RPC只是错误的粒度级别;让它容忍会迫使你编写不完整的程序来完成大量的事情,并采取大量的参数,只是为了避免网络往返(=&gt;许多小调用)API的性能和可靠性。< / p>

在消息方面多考虑一下。考虑在客户端上建立一个消息队列以传递给您的服务器,可能还有与每个消息相关联的回调以处理返回值(如果有的话)(匿名方法非常适合回调!)。然后,一次性将所有消息发送到服务器,并接收和解析结果,根据需要为每个处理的消息调用回调等。

如果您的服务器在处理复合请求(来自客户端的消息队列)处理事务时有某种方法来封装所有状态更改,那么它的工作方式会更好:您可以很好地处理第n天发生的错误消息 - 简单地丢弃在服务器上完成的所有工作,并继续进行,就像客户端请求从未进入过一样。这通常也简化了客户端对服务器状态的理解。

我过去建立了围绕这些原则的系统,并且它们起作用。使网络透明化,假装系统的物理布局不存在,只会造成长期的痛苦。

答案 1 :(得分:5)

至少应该在Delphi 2010中使用DataSnap,最好是在Delphi XE中。你所描述的基本上就是DataSnap。它是一个非常强大的RPC系统,允许您创建可以向客户端提供任何Delphi类型或类的服务器。一旦你有了服务器,客户端就可以创建代理代码来调用服务器,就像它是应用程序的本机部分一样。没有IDL,没有COM,只有干净的Delphi代码。服务器甚至可以生成REST / JSON组合,以便与非Delphi clinets一起使用。

DataSnap正是您所寻找的 - 干净,整洁,强大且简单。

答案 2 :(得分:4)

您应该查看一个像RemObjects SDK这样的框架。您可以使用他们的工具来定义界面,它可以为您完成所有工作。然后,您只需实现这些功能,并在客户端中调用它们。我通过获取业务逻辑组件并将其作为接口,将单个应用程序转换为客户端/服务器应用程序。无需推出自己的线路协议。

答案 3 :(得分:2)

您可以编写一个程序,使用RTTI为您构建必要的单元,而不是手动编写所有内容。我在过去使用包含数千个表的ORM系统完成了这个...编写代码生成器的速度更快,更容易,该代码生成器将从数据库模式中使用系统所需的类和单元进行吐出。

这种方法的另一个优点是它更容易测试,因为它可以通过可预测的行为进行缩放。