在这个过程中,我们称调用方可以调用的是服务的delegate(可以类比为Stub),被调用方注册进来的是服务的implement(可以类比为Skeleton)。路由适配层就是Adaptor。可以基于不同类型的Adaptor构造服务的delegate。服务的implement也可以注册在不同的Adaptor上。不同的Adaptor只需要针对RPC层提供同样的接口,让RPC层可以发送打包消息和服务特定的路由规则,可以注册implement即可保证RPC层与Adaptor层是完全无关的。
我们在示例中实现了多种Adaptor,目前为止涉及到的有MqttAdaptor、GateAdaptor、AmqpAdaptor。
除了这整个的数据流之外,示例中还包装了两种异步调用与回调形式。
第一种专门针对不支持.Net 4.5的平台,比如Unity。但是只要针对这种形式稍加扩展,也能支持.Net 2.0的yield语义,实现简单协程。关于.Net 2.0中的协程实现,可以参考这里。
两种异步方式实现回调的原理是相同的,都是本地hold住调用上下文,等回包的时候检查即可。
这样,在支持.Net 4.5的平台,一个穿插了RPC调用的函数就可以写成这个样子:
1 int a = GetNumber(0); 2 int b = await SceneToSceneMgrService.GetNumber(a); 3 int c = b;
View Code
在Unity中的脚本逻辑,可以这样调用RPC:
1 service.AskXXX(x, y).Callback = 2 operation => 3 { 4 if (operation.IsComplete) 5 { 6 var ret = operation.Result; 7 } 8 };
View Code
关于一些细节的说明
引入新的问题 有了RPC之后,我们可以在应用层以统一的形式进行服务请求。
但是这样还不够——我们目前所提的RPC就是普通的方法调用,虽然对应用层完全隐藏了协议或者其他中间件的细节,但是这样一来这些中间件的强大特性我们也就无法利用了。
还应该有另一种与RPC平行的抽象来特化RPC的形式,这种抽象与RPC共同组成了一种游戏开发规范。
3.3.2 RPC、Pattern与规范
定义问题
由于不同的中间件解决问题的方式不同,因此我们没办法在应用层用统一的形式引用不同的中间件。因此,我们可以针对游戏开发中的一些比较经典的消息pipeline,定义pattern。然后,用pattern与RPC共同描述服务应该如何声明,如何被调用。
Pattern解决了什么问题 不同的基础设施抽象可以实现不同的pattern子集,如果需要新增加一类基础设施,我们可以看它的功能分别可以映射到哪几种pattern上,这样就能直接集成到Phial规范中。
下面,就针对游戏服务端的常见需求,定义几种pattern。
三种通信情景:
最简单的pattern是ask,也就是向服务发起一次异步调用,然后client不关注服务的处理结果就直接进行后续的逻辑。最常见的就是移动请求。
还有一种是传统MMO中不太重视,而异步交互手游反倒从web引入的request。client向服务发起一次异步调用,但是会等到服务处理结果返回(或超时)才进行后续的逻辑。例子比较多,比如一次抽卡或者一次异步PVP。
与ask对应的是sync,是服务进行一次无源的对client的impl调用,client无条件执行impl逻辑。sync需要指明被调用方。这种最常见的是移动同步。有一点需要注意,示例中实现了一种形式比较丑陋的组播sync,依赖了Gate的私有协议,也就是forward指定一个int值。这个之后会做调整。
与request对应的是reply。这个相当于是处理一次request然后直接返回一个值,没什么特别之处。
最常见的就是invoke,相当于一次希望返回值的远程调用。例子有很多,适用于任意两个服务间的通信需求。
还有一种解耦利器我称之为notify。当然本质上其实就是pub-sub,消息会在中间件上dup。应用情景是消息提供者/事件源只管raise
event,而不关注event是否被处理、event后续会被路由到哪里。在中间件上,只要有服务实现了该notify service的impl,就能得到通知;如果没有任何节点提供对该服务的impl,就相当于消息被推到了sink。应用情景是可以将玩家行为log以及各种监控、统计系统逻辑从业务代码中剥离出来,事件源触发的逻辑只有一处,而处理的逻辑可以分散在其他监控进程中,不需要增加一种监控就得在每个事件源都对应插一行代码。