当前位置: 华文星空 > 知识

网游服务器逻辑和传输如何分层/解耦?

2022-01-19知识

题主的描述很详细,基本看懂了。

很多时候,问出正确的问题,很容易得到正确的答案;而如果疑问的角度不好,就会陷入矛盾,无法得到有效的结果。

这个问题就属于后者——实际上,题主是找错了解耦的角度。

PickUp 消息来说。题主的设计问题在于 耦合不足 ,具体来说是PickUp包没有做完该做的事,无效信息过多、有效信息不足,给网络同步带来疑难。

1、PickUp消息包应该包含哪些内容?

客户端和服务器的同步是网络游戏的核心问题之一。无论现代硬件如何进化,仍然有一些基本原则,比如:

  1. 需要同步的数据,应当尽可能小。
  2. 不要去反复更新没有变化的数据。
  3. 不要同步与此次操作无关的数据。

所以在比较标准的设计中,「拾取道具 PickUp」这件事,应有C2S和S2C两个网络包:

C2S ,客户端到服务器的请求。应当包含:

  1. 道具在场景中的ID。(或者道具位置,只要是能够标识出掉落道具的参数)。

消息完毕。

只有这一个参数?

仔细想想,确实如此。在真正的网游中,客户端只要告诉服务器「我要捡哪个道具」就够了,至于那个道具是否存在?距离多远?是什么东西?能不能捡?都是由服务器说了算,不需要也不能够由客户端来决定。

甚至客户端具体是哪个玩家,即PlayerID也是由网络层决定的,客户端无权定义自己的身份。

S2C ,服务器对PickUp事件的响应。应当包含:

  1. 拾取成功或失败,失败原因用错误码表示即可。
  2. 如果拾取失败,无需任何其它数据。消息完毕。
  3. 如果拾取成功,则消息中仅包含 有变化 的道具信息。比如第几个背包格子添加什么道具,或者某个道具的叠加数量+1。总之仅包含变化的道具信息。消息完毕。

这些数据足够满足绝大多数典型的网络游戏,不需要其它任何数据,也不应该添加其它数据。

为什么呢?

2、问题:由此操作引发的其它数据变化,如何处理?

显然,如果游戏比较复杂,比如WOW那种的,拾取新道具的操作可能引发其它一系列变化,包括不限于:

  1. 角色负重变化,某些属性变化。
  2. 任务列表变化。
  3. 技能变化。
  4. 状态(buff)变化。

……等等很多。

但是注意,就算变化再多,这些变化一般也不加入到PickUp消息中。

以任务变化来说,当拾取新的任务道具时,新增道具触发了任务系统的刷新。假如任务有变化,自然会触发任务系统的同步机制,任务系统自然会推送「 有改变的任务 」到客户端。

同理,角色数据、技能系统也都像这样有自己的同步机制,都会在发生改变时,发送「最小改变数据」同步给客户端。

各个模块各司其职。无论如何,它们与「拾取道具」这件事没有直接关系,不归拾取道具的逻辑负责。

3、题主的设计有哪些不妥?

单说从问题描述中看到的一些不妥之处。

1、Net.SendItemUpdate、Net.SendPlayerUpdate等函数,疑似发送了全部数据。

前面提过,全量更新违背了信息最小化原则。不仅让网络包变大,更糟糕的是:当玩家同时发送多个请求时,多个响应的全量包之间的数据是重复的甚至不一致的,数据正确性与处理顺序密切相关,容易引起数据同步的BUG。

2、一个PickUp请求,产生了三个独立的响应。

一个请求产生多个响应,本身没问题。但是具体到题主的代码中,与前文描述的情况不一样。

主要是请求包和响应包应当包含哪些数据、不应当包含哪些数据需要想清楚。

4、关于网络同步能否解耦的问

网络同步有些情况下能解耦,有些情况下应该强耦合,慢慢来谈。

首先,网络同步有三种基本方式:

1、状态同步

服务器和客户端持续高频率同步「所有状态」,经典的例子是早年的Quake网络版。

不要以为同步「所有状态」会很慢。其实,利用diff算法,自动识别差异化信息并同步,能得到极为优异的网络性能。

简单、高速、有效,是写得好的状态同步算法最大的特点。

2、帧同步

帧同步是网络上讨论很多的概念。它的核心就是「仅同步用户操作,不发送任何状态数据」。

帧同步的前提是「操作相同,结果一定相同」。帧同步在原理上不难理解,但实际开发起来有很多疑难。主要是对逻辑代码的编写提出了很高要求,任何一点点逻辑瑕疵都会导致客户端服务器数据不一致。所以真正完全采用帧同步的游戏并不是那么多。

但是从性能和效率上来讲,帧同步确实是王者级别的存在,适合很多高频高速操作的移动端网络游戏。

3、★ 事件同步

虽然前两种同步方式经常听说,但实际上,介于前两者之间、或者说结合了前两者的同步方式才是大部分网络游戏的真实方案。

真正网游项目中,特别是MMO中,很难有一种同步方式打天下的情况。要完美解决所有问题,还是只能定义各种各样不同的逻辑包,具体问题具体分析。

像题主的「拾取道具PickUp」这个包,客户端到服务器的是一个操作(操作:拾取周围某个道具),服务器返回的是一些状态(成功或失败,背包状态、角色状态)。

有时是操作、有时是状态,无法统一命名,我称之为事件同步。或者说是一种很自由的同步方式,完全取决于每一个包的处理怎么写。

搞清楚了几种同步方式的区别,你会发现:帧同步和状态同步,都能做到与具体逻辑代码无关,也就是真正意义上的「解耦」。

在帧同步和状态同步框架下,只要逻辑代码写对了,所有的状态自然就由底层同步了。网络的事情完全不用操心。

而更普遍的「事件同步」方式下,同步机制本身就与游戏逻辑密切相关,开发者不得不精心设计每一个消息包,把每一个包都写对了,才能做到无漏洞、数据一致。

没想清楚这一点的情况下,强行在每一个操作中同步所有数据,模仿「状态同步」的做法,自然就会踩到坑里~~

网络联机游戏的机遇

近几年独立游戏蓬勃发展,但 联机功能完善的创新游戏 发展水平并不高。

这既是市场的短板,也是历史的机遇,希望有志气的小伙伴们考虑这一条充满挑战的道路~~~