« 读完了《代码大全》 | 返回首页 | 心跳服务器 »

目前我们的游戏服务器逻辑层设计草案

我们一开始的游戏逻辑层是基于网络包驱动的,也就是将 client 消息定义好结构打包发送出去,然后再 server 解析这些数据包,做相应的处理。

写了一段时间后,觉得这种方案杂乱不利于复杂的项目。跟同事商量以后,改成了非阻塞的 RPC 模式。

首先由处理逻辑的 server 调用 client 的远程方法在 client 创建出只用于显示表现的影子对象;然后 server 对逻辑对象的需要client 做出相应表现的操作,变成调用 client 端影子对象的远程方法来实现。

这使得游戏逻辑编写变的清晰了很多,基本可以无视网络层的存在,和单机游戏的编写一样简单。

本质上,这样一个系统跟网络包驱动的方式没有区别;但是从编码表现形式上要自然很多。正如 C 语言也可以实现面向对象,但却没有 C++ 实现的自然一样。在这个系统中,引擎封装了对象管理的部分,使得逻辑编写的时候不再需要处理讨厌的对象数字 id ;还隐藏了消息发送或广播的问题。

我把玩家控制的角色,和服务器上你的角色分做两个东西。即,你控制的你,和服务器认为的你就分开了。服务器认为的你,你看见的服务器上的其他人是一类东西。操作自己的角色行动时,你通过 client 上的控制器的远程方法向服务器发送指令;而服务器通过远程调用每个角色的远程方法让 client 可以收到感兴趣的所有角色的行为。

这样,client 永远都是通过一个控制器调用其远程方法来告诉服务器"我要干什么",而服务器的逻辑层则通过调用其上所有逻辑对象的远程方法来改变每个对象的状态。而引擎就根据每个链接的需要,广播这些消息,使得每个 client 上对应的影子对象可以收到状态改变的消息。

这些,就是半个月来我跟同事一起做的工作。当然,由于我们用脚本编写逻辑层,这样,脚本接口可以比 C 接口实现的漂亮的多。

首先是自定义格式的接口描述文件,用自编写的工具自动编译成对应脚本代码。我们只需要在脚本中编写对应的类,就可以自动响应远端调用的方法了。而调用远程方法,也跟本地方法保持同样的形式,写起来跟本地函数调用没有区别。这在以前用 C/C++ 编写逻辑的时候是很难做到的。

其次,引擎内部做好对象的管理工作,负责把通讯协议上的 id 转换成逻辑层中的对象传递给逻辑层使用。

再次,enum 这样的类型再也不需要用一些数字的常数了,也不需要在脚本额外的定义出来。可以在接口文件中定义好,经过引擎的处理后,逻辑层可以直接用更为友好的字符串代替,而不失去效率。

编写逻辑的程序员不再需要关心网络的问题后,就可以把心思放在细节上。

最后,对于实现行为预测来补偿网络延迟的特性上。在先前的版本中,我们为了实现这个,花了不少的气力。主要是将时间戳信息放在基础通讯协议中来辅助实现。具体的消息包收到后,再计算延迟时间来推算当前的状态。现在,可以把时间信息封装到 RPC 中,让每个远程方法自动带有延迟时间,方便计算。按模拟程序的实际效果上看,单单位置同步的预测策略,可以让延迟在 8 秒之内的玩家可以忍受;而延迟小于 1 秒的时候,几乎不会受到滞后的影响了。

关于每个链接感兴趣的信息的问题,决定了每个逻辑对象的状态改变要通知哪些人。目前的想法是独立到单独进程去处理,我们在处理连接的服务器和处理逻辑的服务器之间设置单独的服务器来管理每个链接感兴趣的对象,这个任务相对单一且责任重大,独立出来可以大大减轻逻辑服务器的复杂度。

Comments

可以让延迟在 8 秒之内的玩家可以忍受;而延迟小于 1 秒的时候,几乎不会受到滞后的影响了。这个做了预测补偿,如何保证停下来的时候位置的正确。

不太明白该文是表达什么,呵呵

重用 id 为何是危险的?
TCP 是顺序处理的,假如是服务器向客户端发起删除的请求。即使没有得到回应,接下来的请求要客户端重新创建一个这个 id 的对象(也就是你说的影子对象)也是安全的。因为前一个包一定比后一个包先处理。

你实现的这个 RPC 中的远程对象我还是不太明白。

我想的话,这个远程对象,应该不是 DCOM 的那种通用的、与业务逻辑无关的远程对象,而是特定于网络游戏的。这个远程对象和广播的逻辑是有耦合的。对吗?

不对,全局的表不安全,应该是每个连接一个表。

请问远程对象的查找是怎么做的呢?生命周期又是怎么管理的呢?

是不是需要一个全局的 ID 到对象的表呢?或者是利用服务器端跟逻辑相关的一些表来作查找?

对你举的例子我看不明白。你的意思是 _被调用方_ 重用 ID 需要 _调用方_ 确认?还是说, _调用方_ 请求 _被调用方_ 删除某个远程对象需要等待确认?

大部分回应包是不必要的。

TCP 可以保证数据一定发到(除非中间断线),但不能知道确切的接到时刻,并在逻辑上做出回应。

例如,假设我们用 id 来指代远程对象,如果设计方案里需要 id 重用,而不是用 GUID 。那么发出删除 id 的消息包就需要回应。

因为如果不回应,你不知道远程对象是否已经被删除(虽然他迟早会被删除),那么再没有得到确切消息前重用 id 就是危险的。

云风,请问这样的 RPC 协议每一个调用需要有回应吗?

我想的话,如果把所有 RPC 调用都设计成单向无返回值的,而双方用 TCP 连接。是不是就可以不需要返回回应包?

Right abstracted MMOG logic is a cool thing, but may still cause trouble when it comes to cheaters and macroers....ICE is basically a good thing but the developers simply know nothing about the realistic issues in MMOG...you know I used to be a cracker and knows how many MMOG client-side get ruined , a cool client/server mechanism should consider more the safety issue than easy-to-dev issue....Well that's a choice of coding or hunting cheaters...

我什么时候能到这个地步..

一般来说中型的系统用rpc肯定不会有问题,但是从原理上来说不是最优化的。

为什么不用ICE呢?ICE不适合吗?能谈谈这个吗?

很像我们正在用的这套...
就是 余某 的那套...
给我的感觉,最大的好处是开发更为直观,降低了服务逻辑编写难度

RPC的调试问题好做吗

关于处理 AOI 的独立服务器的问题,我的想法是,任何额外的服务如果能设计成流水线作业就不太小少效率,并能够减少复杂度。

比如,客户端-连接服务器-AOI服务器-逻辑服务器,数据一律是从左到右或从右到左的,不存在中间环节的交互,就会是一个良好的设计。

其实 ip 包从玩家到我们的服务器,中间也不只过了一台机器,我们无非是中在这个流程上加了几台而已。

C++ 也可以完全实现 RPC ,我的感觉是使用复杂度的区别。或者是代码维护难度的区别。动态语言还是很有优势的。

用C++实现RPC也是很容易的,早两年我们用ICE开发网游的时候就全部用RPC实现协议的。不过当初洋人们设计的时候把服务分的太多太细,导致异常高的复杂性,导致我们疲于应付各种时序、同步问题,叫苦不迭。

>>最后一段通过单独的服务器来处理>>感兴趣的对象,
1。独立的服务器处理部分业务逻辑是需要时间的,把这些逻辑通过网络发给独立服务器同时独立服务器把结果返回也是需要时间的,这个时间比在逻辑服务器处理这些逻辑当然要消耗更多的时间,
2。同时独立的服务器要和逻辑服务器上对象状态保持同步,否则他没有处理逻辑所需要的数据。这样部分加大了逻辑服务器的数据通讯量。
如果上面的问题解决不好的话,最终下来最终得不尝失。

虽然有些看不明白,当还是保存一下。hoho

我现在对自动产生网络通讯部分编码解码的代码也很感兴趣。不过我用的是C++,所以对接口的描述文件使用C写得,然后用自己的工具把它翻译成parser。现在我们的项目中,协议可以被自动翻译成二进制格式,XML格式,和Lua的table

用rpc的问题是如果真的把它当作本地接口来设计,必然会产生大量的细粒度接口,而细粒度接口就意味着网络上频繁的交互过程,效率大受影响。
通常在分布式设计中,对于远程接口应该设计成粗粒度的,也就是一次通讯就完成很多事情。有一本书叫企业应用设计模式讲了这个。我觉得挺有道理的。

这有点像ICE了,不过感觉你这个比ICE要轻得多。特别是双向RPC,以前想过感觉实现有点难度就放弃了,看样子还是有希望。

Post a comment

非这个主题相关的留言请到:留言本