使用Delphi实现透明远程操作的最简单解决方案是什么?

3
我是一名有用的助手,可以为您翻译文本。
我有一个用于Win32的Delphi双层应用程序,其中实现了许多业务逻辑,这些逻辑都在一个“上帝对象”中。我想将其外包到一个单独的服务中。该服务应该通过TCP/IP Telnet样式协议由多个客户端访问。
我该如何使过渡变得简单?
具体来说,我想保持这种简单性:我希望只需定义每个函数一次。例如,如果我想要将PIN码登录功能添加到我的应用程序中,我只需要定义即可。
 function Login(Username: string; PinCode: integer): boolean;

如果在服务器上有某个对象中的函数,那么我可以从客户端使用它而无需进行任何额外的工作。

最坏的情况是,我需要实现三个函数而不是一个。首先,在服务器上实现函数本身,其次,在网络上接收文本行的解码器,对其进行拆包并检查其有效性:

 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时,它会打包参数并将其发送到服务器:
function TServerConnection.Login(Username: string; PinCode: integer): boolean;
begin
  Result := StrToBool(ServerCall('Login '+Escape(Username)+' '+IntToStr(PinCode)));
end;

显然,我不想要这个。

到目前为止,我已经成功摆脱了unmarshaller。通过使用Delphi RTTI,我编写了一个通用的unmarshaller,它会查找名称相同的已发布方法,检查参数并调用它。

现在,我只需要向服务器对象添加一个已发布的方法,就可以从telnet中调用它了:

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

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

那么我该如何编写一个marshaller呢?我不能动态获取服务器函数列表并将函数添加到客户端对象中。 我可能可以保留一些动态的伪函数集合,但这样会让我的客户端调用变得难看:

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

此外,这种方法会破坏类型安全性,因为每个参数都作为变量传递。如果可能的话,我想保持编译时的类型安全。
也许可以自动生成客户端代码?我可以在服务器端编写“GetFunctionList()”和“GetFunctionPrototype(Name: string)”:
> GetFunctionList
Login
Logout
IsLoggedIn

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

因此,每次需要更新客户端时,我只需从服务器重新查询所有函数原型,并自动生成marshaller代码。但这会混合编译和执行:我必须先编译服务器,然后启动它并查询其函数,然后构建客户端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类的函数调用之外,没有办法实现自动检测断开连接和自动恢复连接功能:
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中进行远程控制是否有常见的模式或现成的解决方案?

2
请注意,n层应用程序比2层应用程序更复杂。一些逻辑可能需要重新设计。服务器可能是无状态的(客户端应该有一种方法来发送它们的状态),或者是有状态的(你必须在服务器上处理每个状态)。你可能会遇到多线程和可扩展性问题,因为现在一个服务器应该执行n个客户端的任务。你必须保护敏感数据的通信渠道,并处理身份验证和授权。你不使用n层来简化代码。你使用它来更好地控制应用程序及其数据。 - user160694
4个回答

7
个人认为,你不应该基于透明远程调用来设计分布式系统。过程级别的RPC对于强大且高效的客户端-服务器交互来说是错误的粒度水平;让它变得可容忍将会迫使你编写定义不清的过程,这些过程需要做很多事情并且需要很多参数,只是为了避免网络往返和API调用的频繁(即许多小调用)所带来的性能和可靠性损失。
更多地考虑消息传递。考虑在客户端上建立一个消息队列,可以将其传递给服务器,或许还可以与每个消息关联回调来处理返回值(如果有的话,匿名方法对于回调非常有效!)。然后,一次性将所有消息分派到服务器上,并接收和解析结果,根据需要为处理的每个消息调用回调等。
如果您的服务器有一种方式来封装处理复合请求(来自客户端的消息队列)中的所有状态更改为事务,则效果更好:您可以得到一个很好的处理第n个消息发生错误的方法 - 只需放弃在服务器上完成的所有工作,然后继续进行,就像客户端请求从未进入一样。这通常也简化了客户端对服务器状态的理解。
我过去建立过以这些原则为导向的系统,它们起作用了。将网络透明化,假装系统的物理布局不存在,只会在长期带来痛苦。

感谢您的有趣评论。我已经在其他类型的服务器上完成了这个任务,但是这个服务器主要由客户端函数组成,这些函数大多是自给自足的,并且调用很少。我可以接受网络延迟,而且如果我需要异步发送,我可以轻松地在底层切换并模拟仅透明调用的同步回复。 - himself

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

2
没有安全性... 特别是在 XE 之前的版本中(除非您使用使用 DCOM 安全性的 DCOM 版本)。当然,您可以始终以明文或弱安全性发送您的 PIN 码,密钥从未更改并存储在本地... - user160694
听起来不错,我会试一下的,谢谢。唯一让我担心的是它似乎有同样的缺点:你需要先编译和运行服务器,然后才能为客户端生成代码。不确定这将如何集成到自动构建中。 - himself
只要您使用正确的方式来保持服务器和客户端代码同步,就不会有问题。一旦生成了客户端存根,除非更改服务器,否则无需重新生成它。将它们推送到版本控制系统中,当自动构建工具拉取代码时,只需编译服务器和客户端即可 - 它不需要重新生成任何内容。 - user160694
现在,DataSnap和SSL支持已经内置了安全性,但我仍然更喜欢RemObjects SDK和DataAbstract。 - Warren P

4

你应该看一下像 RemObjects SDK 这样的框架。使用他们的工具定义接口,它会为你完成所有工作。然后只需实现函数,在客户端调用即可。我通过将业务逻辑组件变成接口,已经将单个应用程序转换为客户端/服务器应用程序。没有必要自己编写通信协议。


只是提到components4developers的kbmMW作为另一个适合这种情况的好框架。 - Marjan Venema

2
您可以编写一个使用RTTI构建所需单元的程序,而不是手动编写所有内容。我曾经在ORM系统中使用过这种方法,该系统包含数千个表格...编写代码生成器以从数据库模式生成必要的类和单元比手动编写更快且更容易。
此方法的另一个优点是易于测试,因为它可以按可预测的方式进行扩展。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接