« 为什么美术和策划在使用 git 时会遇到更多麻烦 | 返回首页 | 用 gdb 分析 coredump 的一些技巧 »

MMORPG 客户端的网络消息框架

昨天和人闲扯,谈到了 MMORPG 客户端的网络消息应该基于怎样的模型。依稀记得很早写过我的观点,但是 blog 上却找不到。那么今天补上这么一篇吧。

我认为,MMO 类游戏,服务器扮演的角色是虚拟的世界,一切的状态变化都是在游戏服务器仲裁和演化的。而客户端的角色本质上是一个状态呈现器,把玩家视角看到的虚拟世界的状态,通过网络消息呈现出来。所以、在设计客户端的网络消息分发框架时,应围绕这个职责来设计。

客户端发起的请求分两种:一种是通知服务器,我扮演的角色状态发生了改变,请求服务器仲裁;另一种是,我期望获取服务器对象的最新状态。后一种有时以服务器主动推送来解决,也可以用主动请求,两者主要的区别在于流量控制。

其实、对于客户端作为状态呈现这个角色而言,请求和回应之间的关系并没有什么紧密联系,并不适合使用基于 RPC 的网络模型。这个无关乎 RPC 的实现是用 callback 还是用 coroutine 。

这是因为,当服务器的状态改变时,客户端关心的应该是这类事情发生时,我应该如何呈现;而不是具体到某个请求发出后,收到回应我应该怎么处理。RPC 把请求和回应紧密耦合在一起,让回应的处理流程强依赖请求时的上下文,这样容易引起不必要的状态管理。

比如,我看到我们公司的一个项目中曾经出过这样的 bug :UI 界面上点击一个按钮,用 callback 的形式发起了一次 RPC 调用;在回应的 callback 函数中对 UI 界面上的元素做了一系列的修改。可是在回应的网络消息收到时, UI 界面已经关闭了,由于处于节约内存的考虑,还触发了一些对象销毁流程,结果因为操控不存在的对象造成了 bug 。

你可以从别的角度来看待这个 bug 。比如说应该建立一个更稳固的 UI 框架,做更严谨的生命期管理。但我认为,根源问题就是没有把请求和回应解耦造成的。

我们应该这样看待按钮点击事件:它只是触发了一个事件在服务器上运行,这个事件导致了某个服务器上的对象状态改变了,而回应包只是通知了状态改变的结果。客户端真正要做的是怎样正确呈现这个结果。

如果我们把客户端处理网络包的流程都归纳成类似的模型,统一成一个简单的框架就很容易了。

客户端根据收到的网络包进行相应的处理,无论这个网络包是服务器的推送、还是对之前客户端发起请求的回应。在这个框架下,只关心来了一个网络包后的类别,根据这个类别来决定要做哪类事情。处理流程是关联在消息类别上的,而不是关联在单个请求上的。

对于请求回应的处理,其实和推送的处理并没有本质的不同。只是实现上略有差别。对应回应包的处理,处理流程得到的参数不仅仅是网络包,还需要有对应的请求数据(也就是客户端当初自己发起请求的参数)和这个事件绑定的客户端本地对象。

通常我们会用一个 session 号来关联回应包和请求包,在发起请求时,不仅把请求内容打包发给服务器,同时还把它记录在本地,用 session 关联起来;这样在收到请求时,可以根据 session 找回当初请求的参数,以及请求的类型,这样就可以不必让服务器推送完整的状态值,客户端可以自己找到匹配的内容。

在大部分情况下,我们还需要在发起请求时,给这个 session 绑定一个本地的对象。虽然请求本身肯定有这个信息(否则服务器就不知道该请求到底操作呢什么东西),但额外的一个本地对象使用起来更方便,可以用来携带少量本地状态信息。在回应抵达时,直接操作这个绑定对象写起来更方便。

如果用 lua 来实现,看起来大致是这样的:

send_request(obj, req_type, req_data) -- 发起一个请求,请求类型为 req type ,参数是 req data ,绑定对象为 obj 。

function net.req_type(obj, req_data, resp_data) -- 定义一个函数来处理 req_type 这种类型的消息,可以获得发起请求时的 obj 和 req data 以及服务器的回应 resp data 。

Comments

云风大神,想了解一下服务器主动推送数据给客户端的过程中如果因为超时导致发送失败后,后续的客户端包如何处理呢?或者说服务器如何正确的把漏发的包补发给客户端
云风大神,想了解一下服务器主动推送数据给客户端的过程中如果因为超时导致发送失败后,后续的客户端包如何处理呢?或者说服务器如何正确的把漏发的包补发给客户端
用ajax技术做游戏框架,文本处理,血量数据变化等的 数据语言,协调,其余的交给显卡显存,网络通讯部分,双向处理游戏流程以及进行开发设计,如何?
听说简约要卖了么 云风老大要发了
这文章写的返璞归真了,哈哈。 其实不要session问题都不大,95%的情况下并不需要在收包时找回发送时的数据,只是为了某些情况下的方便而已。 如果没有session,甚至可以在发包时发送额外的“原样返回”字段来解决。 简单来说,N年前(N>10)网游都是这么做的,那阵都没几个人想到session这回事
@limengbin 因为关注是发送到守卫对象上的,当它被关注达到一个限度(计数)后,这个对象可以选择性的回复一些比较久远的请求。而这些请求得到回应后,因为已经距离太远(或离开了场景),自然就不会提下一个请求了。 不需要由观察者自己主动取消。
请问需不需要再加一个取消请求的功能?用来防止长期不动的对象身上挂了太多请求没有回应。 比如城门边的守卫,路过的人可能都会请求下,但是由于他不动,第二次请求会处于等待状态。如果他一直不动,他身上的请求列表会一直涨直到爆表。应该玩家走的太远,要主动取消掉一些不再关心的请求吧
其实就是用客户端的session号,来构建了一个回调(保存请求,保证了上下文)。所以在返回的包里,只需要session号,就少流量

Post a comment

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