him*_*elf 3 delphi remoting rpc design-patterns rtti
我有一个两层Delphi for Win32应用程序,在一个神对象中实现了很多业务逻辑,我想将其外包到一个单独的服务中.多个客户端应通过TCP/IP telnet样式协议访问此单独的服务.
我如何才能使过渡变得最简单?
确切地说,我想保持这种简单性:我想只定义一次所有功能.例如,如果我想在我的应用程序中添加PIN码登录功能,我只需要定义
function Login(Username: string; PinCode: integer): boolean;
Run Code Online (Sandbox Code Playgroud)
在服务器上的某个对象中运行,然后我可以从客户端使用它而无需任何额外的工作.
在最坏的情况下,我必须实现三个功能而不是一个.首先,函数体本身在服务器上,第二,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;
Run Code Online (Sandbox Code Playgroud)
第三,marshaller在被客户端调用时打包params并将它们发送到服务器:
function TServerConnection.Login(Username: string; PinCode: integer): boolean;
begin
Result := StrToBool(ServerCall('Login '+Escape(Username)+' '+IntToStr(PinCode)));
end;
Run Code Online (Sandbox Code Playgroud)
显然,我不想要这个.
到目前为止,我已经设法摆脱了unmarshaller.使用Delphi RTTI,我写了一个通用的unmarshaller,它按名称查找已发布的方法,检查params并调用它.
所以现在我可以将已发布的方法添加到服务器对象中,我可以从telnet调用它:
function Login(Username: string; PinCode: integer): boolean;
> login john_locke
Missing parameter 2 (PinCode: integer)!
Run Code Online (Sandbox Code Playgroud)
但是我怎么做关于编写编组的人呢?我无法动态获取服务器功能列表并向客户端对象添加功能.我可以保留一些动态伪函数集合,但这会让我的客户端调用丑陋:
ServerConnection.Call('Login', [Username, Password]);
Run Code Online (Sandbox Code Playgroud)
此外,这会破坏类型安全性,因为每个参数都作为变体传递.如果可能的话,我想保持编译时类型安全.
也许客户端代码自动生成?我可以在我的服务器中写"GetFunctionList()"和"GetFunctionPrototype(Name:string)":
> GetFunctionList
Login
Logout
IsLoggedIn
> GetFunctionPrototype Login
function Login(Username: string; PinCode: integer): boolean;
Run Code Online (Sandbox Code Playgroud)
因此,每次我需要更新客户端时,我只需从服务器重新查询所有函数原型并自动为它们生成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;
Run Code Online (Sandbox Code Playgroud)
这节省了我每次编写手动包装(错误的可能性更小),但仍然需要我手动将服务器功能原型复制到客户端对象中.另外,写这个通用编组可能会是一个活生生的地狱.
然后有使用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;
Run Code Online (Sandbox Code Playgroud)
我们回到了几个功能而不是一个功能.
那么,你们会建议什么?我缺少的其他选择是什么?在Delphi中是否有常见的模式或远程处理的现成解决方案?
就个人而言,我认为您不应该构建基于透明远程处理的分布式系统.对于健壮且高效的客户端 - 服务器交互,过程级RPC只是错误的粒度级别; 让它可以忍受将迫使你编写不完整的程序,完成大量的事情,并采取大量的参数,只是为了避免网络往返(=>许多小调用)API的性能和可靠性.
从消息的角度思考更多.考虑在客户端上建立一个消息队列以传递给您的服务器,可能还有与每个消息相关联的回调来处理返回值(如果有的话)(匿名方法非常适合回调!).然后,一次性将所有消息分发到服务器,并接收和解析结果,根据需要为每个处理的消息调用回调等.
如果您的服务器在处理复合请求(来自客户端的消息队列)处理事务时有某种方式来封装所有状态更改,那么它可以更好地工作:您可以很好地处理第n条消息上发生的错误 - 只需放弃在服务器上完成的所有工作,并继续进行,就像客户端请求从未进入过一样.这通常也简化了客户对服务器状态的理解.
我过去建立了围绕这些原则的系统,并且它们起作用.使网络透明,假装系统的物理布局不存在,只会造成长期的痛苦.
绝对应该至少在Delphi 2010中使用DataSnap,最好是在Delphi XE中.你所描述的基本上就是DataSnap.它是一个非常强大的RPC系统,允许您创建可以向客户端提供任何Delphi类型或类的服务器.一旦你有了服务器,客户端就可以创建代理代码来调用服务器,就像它是应用程序的本机部分一样.没有IDL,没有COM,只有干净的Delphi代码.服务器甚至可以生成REST/JSON组合,以便与非Delphi clinets一起使用.
DataSnap正是您所需要的 - 干净,整洁,强大且简单.