剑网3同步策略

剑网3同步策略

学习教程tuzi2014-07-27 15:13:19A+A-

剑三同步策略

1.      概述

1.1.  什么是同步策略

同步策略是指将玩家所需要的数据,从服务器端传送给客户端的方法。

最简单、最原始的同步策略是将服务端上的所有信息都传送给客户端、每次服务端上的数据发生变化,都立刻通知所有客户端。显然,这种方法是不行的,带宽和计算量都承受不起。那么,就需要减少同步的数据种类,并且不需要每次数据变化都立刻通知客户端。

因为玩家位置的变化频率高、对游戏感受影响最大,所以抽取出来单独讨论。

1.1.1.      位置同步

由于带宽和计算量的限制,每个客户端仅同步周围的9Region,这样可以极大的减少了同步数据量。但是因此跨越Region需要做一系列特殊处理。

剑网3同步策略 技术文章 西山居开发 金山软件 剑网3 剑侠情缘网络版叁 学习教程 第1张

某玩家A,原先在第6Region,现在移动到了第11Region中。那么他的同步范围就从12356791011,变成了678101112141516

需要做的处理有:

1.             通知玩家A,删除无需同步的Region中的场景物体(包括玩家、NpcDoodad、子弹,下同)。对于玩家A来说,需要删除的是123595Region

2.             通知123595Regoin中的玩家,玩家A已经移出了他们的同步范围。

3.             通知玩家A8121415165个新Region中已存在的场景物体。

4.             通知8121415165Region中的玩家,玩家A进入了他们的同步范围。

处理1中的操作可以全部在客户端完成。当玩家A在客户端从第6Region跨入第11Region时,会更新自己的同步范围,自动删除无需同步的Region中的场景物体,无需服务端通知。

处理2中的操作可以由广播的移动指令代替。当123595Region中的玩家收到玩家A的移动指令时,玩家A会最终走出他们的同步范围,走到第11Region中,客户端可以在这时自动删除掉玩家A,无需服务端通知。

现在需要考虑的是如何减少处理3和处理4中传输的数据量。对此,剑网一和剑网三,有不同的策略,详细分析见后。

1.1.2.      其他信息的同步

可能会以较高频率变化、并且玩家关心的数据还有生命值和人物状态,可以考虑特别处理。剩余的其他数据,都可以靠指令方式同步,即客户端需要时向服务端请求,或者服务端发生变化时通知客户端。

1.2.  本文档讨论的要点

1.       明确同步策略的设计目标、运行环境参数、优劣的判断标准

2.       描述曾经提出过的若干种方案

3.       对比各种方案的优缺点,从而得出结论

2.      设计目标

2.1.  解决大规模群战时的延时问题

当玩家持续朝一个固定方向移动时,Region中的所有的新NPC和玩家都必须在他跨入该Region之前完成同步。对于9Region同步来说,这个时间间隔就是玩家跨越一个Region所需要的时间。

由此得到公式:Interval = RegionWidth / PlayerVelocity

其中RegionWidth为恒定值16m

PlayerVelocity[0,16]m/s

因此Interval[1,+]s

如上所述,设计目标为最长在2秒内,同步完所有新Region3-5个)中的NPC和玩家。如果可以优先同步移动方向上的Region,可能效果可以更好。

为了实现这个设计目标,将格子的尺寸从0.5m*0.5m改成了1m*1m2005.05.09)。

2.2.  降低占用的带宽

降低带宽包括以下三方面:

1.         降低平均传输量,根据用户状态调整同步频率;

2.         降低同步数据占总数据比例,尽量利用命令同步的信息,以减少状态同步;

3.         削平峰值,如果需要一次性同步大量数据(跨Region),需要分次同步。

2.3.  缓解客户端与服务端的数据不同步现象

主要是坐标的不同步,其次是血量和人物状态

3.      环境参数与名词约定

3.1.  说明

参数分为四种:

l  常数,这个无需解释了;

l  设计指标,即预想需要达到的运行环境参数,一般会分为一般指标和峰值指标;

l  经验数值,没有经过实际测量,而是根据经验评估得到的数值,所有未标注的来源的数值,都是经验数值;

l  实测数值,经过剑网测量得到的数值,会附上数值的测试条件和原始记录。

3.2.  参数列表

1.       同时在线的玩家数量                   一般指标       4000       峰值指标       6400

2.       在城市内的玩家比例                   设计指标       40%

3.       在野外的玩家比例                      设计指标       30%

4.       在副本内的玩家比例                   设计指标       30%

5.       参加群战玩家的数量                   一般指标       200         峰值指标       300

6.       每个RegionNPC数量             设计指标       <=1.5

7.       玩家移动的最高速度                   常数              16m / s = 1格子/

8.       每组服务器的最大带宽               常数              100 Mb/s

9.       每个客户端的平均上行带宽        实测数值       180 Byte/s                    1

10.   每个客户端的平均下行带宽        实测数值       1.7 KB/s                       1

11.   游戏每秒运行的帧数                   常数              剑网一:18      剑网三:16

 

1:原始数据参见文档《剑网客户端数据包分析》,这两个数据的计算公式如下:

平均流量 城内平均流量×40%+城外打怪平均流量×60

其中的40%和60%由234这三项设计指标得到。

3.3.  名词约定

1.         状态同步       无论数据是否变化,定时的将数据传送给客户端的同步方法

2.         指令同步       仅当数据发生变化或者客户端发起请求时,才将新数据传送给客户端的同步方法

3.         Npc               对于剑网一,Npc中包含了玩家;而对于剑网三,Npc和玩家是完全分开的

4.         逻辑数据       剑三中的逻辑数据是指:角色的当前坐标、目标点坐标、移动速度、角色状态机状态、显示相关的魔法状态、当前生命值百分比、当前内力值百分比、当前怒气值百分比。这些数据的特点是:1 平时同步时必须同步的基本数据;2 改变的频率非常高

5.         显示数据       剑三中的显示数据是指:角色的显示资源、角色的名字、角色的阵营。这些数据的特点时:1 只和显示以及攻击判定有关;2 一般不会改变

4.      剑一的同步策略描述与分析

4.1.  概述

剑网一的数据同步主要依靠每2帧(剑网一每秒18帧)发送一次的状态同步数据包实现。当遍历Region时,每间隔一帧,会从本RegionNpc(包括玩家)列表、Obj(对应剑三的Doodad)列表中各挑选出一个,将其数据组成对应的同步数据包,发送给本Region以及邻接的8Region中的所有玩家。从另一个角度来看,每个玩家每秒最多会收到9×981Npc的状态同步数据包。

4.2.  伪代码与数据结构

4.2.1.      伪代码描述

 

4.2.2.      协议包结构定义

typedef struct tagNPC_NORMAL_SYNC : public tagProtocolHeader

{

    DWORD  ID;               //Npc或者玩家的ID

    DWORD  MapX;             //X坐标

    BYTE   Camp;             //阵营

    BYTE   State;            //状态

    DWORD  MapY;             //Y坐标

    BYTE   LifePerCent;      // 生命的2048分之几的低8位

    BYTE   Doing;            // 高3位为生命的2048分之几的有效高3位,低5位为Doing

} NPC_NORMAL_SYNC;

sizeof(NPC_NORMAL_SYNC) = 17

 

typedef struct tagOBJ_SYNC_STATE : public tagProtocolHeader

{

    BYTE   m_btState;

    int    m_nID;

} OBJ_SYNC_STATE;

sizeof(OBJ_SYNC_STATE) = 6

4.3.  计算与分析

       至此,我们很容易就可以计算出每个玩家的最大状态同步流量:

              Fmax = 17 * 81 = 1377B/s

       唯一会影响这个数值的因素,是同步范围内的9Region中是否有空的Region。由于存在空Region的概率不大,因此我们可以近似的认为

              Favg = Fmax * 0.9 = 1240B/s

       对于单个客户端,状态同步流量占平均下行流量的比例为:

              P = Favg / 1700 = 73%

同时,根据第二章中的环境参数,可以计算出一组服务器(也就是一个完整的游戏世界)的状态同步流量:

F1 = Favg * 4000 = 4.96MB/s = 39.7Mb/s

F2 = Favg * 6400 = 7.94MB/s = 63.5Mb/s

       假设一个Region中有NNpc(包括玩家),则依靠状态同步得知这个Region中的所有Npc(包括玩家)的时间为:

              T = N / 9

 

       -------------我是分割线,计算至此结束------------

 

       从以上计算大结果,很容易看出Npc(包括玩家)状态同步的流量是优化带宽占用的关键。因此,其他数据的就没有分析的必要了。

       剑网一的状态同步策略的特点:

1.       总带宽只和玩家数量有关,且是线性关系。因此玩家数量确定时,占用带宽非常稳定,不会出现波动。

2.       玩家跨越Region时,同步到新Region中的所有Npc(包括玩家)的时间,与Region中的Npc(包括玩家)的数量成正比。

5.      剑三的同步策略描述与分析

5.1.  对剑网一优化余地的讨论

首先,再次明确一下优化的目标(详见第二章)。最重要的是提高群战效率,其次是减少带宽占用,再次是改善用户感受。

具体的优化手段如下:

5.1.1.      消除所有的冗余数据

所谓冗余数据,也就是无效的信息。状态同步的数据,单单对于发现新玩家这一功能来说,有很大的冗余比例。因为状态同步数据的发送,并不考虑客户端是否已经知道这个玩家,而只是不断的循环发送。如果我们要提高发现新玩家的效率,那么只有提高状态同步的频率。同时,状态同步还负责校正客户端数据,而校正客户端数据,并不需要很高的发送频率,这就带来了矛盾。

所以,如果我们能把状态同步中发现新玩家这个功能剥离开,有新的协议和代码来实现,就可以很容易的消除冗余的信息。而状态同步本身,由于剥离的发现新玩家的负担,只需要负责校正客户端数据,发送的频率和数据包的大小都可以降低很多。

5.1.2.      细分状态同步中各种数据的同步频率

区分城内和野外玩家的同步策略。城内和野外玩家的关注点是不同的。在城内时,玩家并不会关注周围人的生命值和内力值(如果剑三还像剑一一样城内不能战斗的话),而且这些数据也不会经常改变,所以它们的同步频率就可以适当降低。

区分战斗和非战斗状态玩家的同步策略。非战斗状态时的同步频率可以少许降低,这时对于Npc的生命值和内力值的关注度不高。

区分玩家的目标和非目标的同步策略。战斗时,玩家对于战斗目标的生命值和状态是非常敏感的,对周围玩家或者Npc的生命值和状态的敏感程度要低一些。

5.1.3.      依据移动方向决定同步数据的发送顺序

 

5.1.4.      尽最大可能利用每一个Bit

对于剑网三,同步坐标点的最大范围是:

X [0, CELL_LENGTH * REGION_GRID_WIDTH * MAX_REGION_WIDTH - 1]

Y [0, CELL_LENGTH * REGION_GRID_HEIGHT * MAX_REGION_HEIGHT – 1]

其中,

CELL_LENGTH = 32

REGION_GRID_WIDTH = 32

REGION_GRID_HEIGHT= 32

MAX_REGION_WIDTH = 64

MAX_REGION_HEIGHT= 64

因此,

       X [0, 2^15 – 1]

       Y [0, 2^15 – 1]

这样,一对坐标只需要4Bytes就可以表示了

-----------------------我是分割线-----------------------

在同步数据包中,另一个占用空间很大的就是Npc或者玩家的ID了。

一个方法是客户端接收到了服务端第一次同步的ID之后,上传一个客户端约定的1Byte或者2Byte的对应序号,以后的同步采用短序号。这样做的主要出发点是,客户端只知道有限的Npc和玩家,短一些的序号足够表达了。其代价有三方面:

1.       在服务端,为每个玩家连接维护一个长短序号对应表

2.       在服务端和客户端,发送和接收数据包时,需要进行序号的转换

3.       可能会带来一些短序号重复的问题

如上所述,这种方案的代价较大。

 

另一个方法是服务端尽可能将某些ID相同的数据包合并,这样可以共享一个ID。按照状态包的发包频率,区分成若干数据集合,这些集合分别是每1秒发送一次的、每10秒发送一次的、每1分钟发送一次的(间隔时间的只是举例)。

5.2.  新同步策略的描述

5.2.1.      移动坐标同步

问题和难点:如何保证Npc或玩家在服务端与客户端的移动路径和位置一致,Npc或玩家在不同客户端的移动路径和位置一致。由于网络延时等原因做到完全一致是不可能,但是要做到较好的玩家体验,移动流畅,操作自如。存在三种影响玩家体验的情况:

1.  玩家发出移动操作后,不能立即响应,如《传奇》

2.  当游戏卡的时候,移动时经常将角色拉回到原来的位置,如《剑网》

3.  移动流畅操作自如,但是瞬移外挂容易横行,如《魔兽》

这是由于不同的技术手段和做法引起的问题:

1.    移动完全以服务端为准,客户端移动前与服务端同步

2.    移动以客户端为准,服务端校验并修正

3.    移动完全以客户端为准

《剑网3》是如何解决的?此处只讨论同步,不讨论服务端客户端如何进行移动处理。

 

《剑网3》策略:变种的以客户端为准,服务端校验修正;

如何实现的呢?

1.  历史位置记录,服务端记录角色一段时间内的位置坐标,比如记录角色最近10秒钟每帧的位置坐标和速度等数值;

2.  同步源坐标和目标坐标,移动指令数据包中携带角色移动时的其实位置及目标地址;

3.  同步移动时刻帧,客户端及服务端在移动同步时,通知事件发生时刻帧,剑网3所有数据包都携带了此信息。

剑网3同步策略 技术文章 西山居开发 金山软件 剑网3 剑侠情缘网络版叁 学习教程 第2张

 

服务端处理:角色的移动主要由客户端触发,服务端进行校验处理;当服务端收到数据包后,校验数据包中源坐标与当前服务端角色源坐标是否相等,如相等服务端角色移动处理,否则修正客户端移动位置及状态。

但是网络同步存在延时,只是这样不加其他的处理的进行校验,会存在很多数据包都不能通过校验,这将会有如上第二种不好的操作感。剑网3对校验做了改进,方法就是校验前预处理,根据数据包中的移动时刻帧取到此时刻服务端角色的位置坐标,再进行源坐标校验。

存在两种可能性,1、数据包中的帧数小于服务端当前运行帧(客户端跑的慢) 2、数据包中的帧数大于等于服务端当前运行帧数(客户端跑的快)

Ø  客户端慢:所有数据包基本都应该属于这种情况,针对这种情况,采用了回滚机制,历史位置记录上场了,服务端首先将角色的位置回滚到数据包帧时刻位置,然后进行校验工作;

Ø  客户端快:针对这种情况,服务端会进行移动处理,赶上客户端快的部分,然后进行校验。不过这属于异常情况,如果出现,三个原因:1、服务端机器太差,撑不住;2、客户端Bug3、恶意外挂。因为客户端的运行帧是服务端同步的,正常情况下客户端不可能快。

通过这样处理校验通过的机率很高,基本上不可能出现频繁修正客户端角色位置,达到避免第二种操作感的效果。

由于服务端对客户端的移动同步坐标进行校验修正,外挂是很难得逞地,避免瞬移。

      

客户端处理:客户端需要做的事情有4

1.     响应玩家移动操作,这个响应是立即的,只要玩家操作键盘移动角色,客户端立即做出移动的响应,并向客户端发出移动同步数据包。这样就不存在第一种不好的体验;

2.     处理其他角色移动,位置完全以服务端为准,当服务端同步过来的源坐标与客户端的当前的坐标不相符时,客户端首先将此角色移动到服务端的位置,再进行移动处理;

3.     处理自身角色移动,有可能服务端主动触发角色移动,如击飞等技能;

4.     处理自身位置修正,将自己移到服务端同步过来的位置,很少发生。

 

存在的问题:客户端在处理其他角色移动时,完全以服务端为准,可能会出现频繁修正其他角色位置,不过这对玩家体验影响不大。有熊掌还要鱼,太贪了吧。

5.2.2.      Region的处理

在剑网一中,玩家跨Region时,并不立刻通知相关的客户端;而在剑网三中,会有特定的数据包通知客户端。

客户端发现: 剑三中,新Npc和玩家的发现,主要依靠客户端的发现。如果客户端收到一个指令广播包(无论什么指令,跳跃、移动、发技能等等),发现其中的角色ID本地不存在,就会向服务器请求该ID的详细逻辑数据(逻辑数据的定义见名词约定部分)。

此处带来一个问题,非活动玩家(不会发送广播包的玩家,比如摆摊的)就永远不会被同步到了,这个问题通过强制同步机制解决。

强制同步:就是当角色进入一个新Region后若干秒(现在暂定2秒),强制发送在2秒内没有活动过的角色的资料。

实现方法如下:

首先:在所有的Npc和玩家的身上增机加两个个变量

1.  m_nLastBroadcastFrame,记录上次做广播行为的游戏帧数;

2.  m_nEnterRegionFrame[9],记录跨入周围9Region时的游戏帧数。

 

其次:在角色的每次Activate时,检查各个该Region的进入时间是否到了强制同步的时刻,如果正好到了,则发送强制同步信息。由于m_nLastBroadcastFrame的存在,我们可以避免发送所有在本角色进入该Region后发送过广播指令的角色的逻辑数据。

 

第三:变量更新

1.  只要有广播数据包发送就更新m_nLastBroadcastFrame成当前游戏帧数。其中,广播位置并不仅仅是位置的状态同步,还包括移动指令等一系列包含了玩家位置的广播数据包;

2.  每次跨Region的时候,都会将新进入的Region对应的m_nEnterRegionFrame[]刷新成当前游戏帧数。

5.2.3状态同步

 

5.3.  新同步策略的延时和带宽占用分析

//通知新加入Region的角色    :    17 Byte

struct G2C_NEW_CHARACTER_INTO_REGION : DOWNWARDS_PROTOCOL_HEADER

{

    unsigned    m_dwCharacterID :   32; //角色ID

    unsigned    m_nX            :   (MAX_REGION_WIDTH_BIT_NUM +

                                     REGION_GRID_WIDTH_BIT_NUM +

                                     CELL_LENGTH_BIT_NUM);  //角色的X坐标

    unsigned    m_nY            :   (MAX_REGION_HEIGHT_BIT_NUM +

                                     REGION_GRID_HEIGHT_BIT_NUM +

                                     CELL_LENGTH_BIT_NUM);  //角色的Y坐标

    unsigned    m_nVelocityXY   :   (CELL_LENGTH_BIT_NUM);//角色在XY平面上的速度

    unsigned    m_Doing         :    5; //角色状态

    unsigned    m_Reserved      :    2; //保留

    unsigned    m_nDestX        :   (REGION_GRID_WIDTH_BIT_NUM +

                                     CELL_LENGTH_BIT_NUM +

                                     MOVE_DEST_RANGE_BIT_NUM + 1); 

                                    //移动目标点X坐标上相对当前点的偏移

    unsigned    m_nDestY        :   (REGION_GRID_HEIGHT_BIT_NUM +

                                     CELL_LENGTH_BIT_NUM +

                                     MOVE_DEST_RANGE_BIT_NUM + 1);

                                    //移动目标点Y坐标上相对当前点的偏移

    unsigned    m_nLifePercent  :    LIFE_PERCENT_BIT_NUM;  //生命百分比

    unsigned    m_nManaPercent  :    MANA_PERCENT_BIT_NUM;  //内力百分比

    unsigned    m_nRagePercent  :    RAGE_PERCENT_BIT_NUM;  //怒气百分比

    unsigned    m_MagicState    :    MAGIC_STATE_BIT_NUM;   //要显示的魔法状态

};

 

//通知客户端一个Region中的所有角色

struct G2C_ALL_CHARACTER_IN_REGION : UNDEFINED_SIZE_DOWNWARDS_HEADER

{

    unsigned    m_dwRegionX :MAX_REGION_WIDTH_BIT_NUM;//Region在地图中的X坐标

    unsigned    m_dwRegionY :MAX_REGION_HEIGHT_BIT_NUM;//Region在地图中的Y坐标

    struct  KSyncCharacter      //15 Byte

    {

        unsigned    m_dwCharacterID :   32; //角色ID

        unsigned    m_nX            :   (REGION_GRID_WIDTH_BIT_NUM +

                        CELL_LENGTH_BIT_NUM);   //角色的X坐标在Region中的偏移

        unsigned    m_nY            :   (REGION_GRID_HEIGHT_BIT_NUM +

                        CELL_LENGTH_BIT_NUM);   //角色的Y坐标在Region中的偏移

        unsigned    m_nVelocityXY   :   (CELL_LENGTH_BIT_NUM); 

//角色在XY平面上的速度

        unsigned    m_Doing         :    5; //角色状态机的状态

        unsigned    m_Reserved      :    6; //保留

        unsigned    m_nDestX        :   (REGION_GRID_WIDTH_BIT_NUM +

                                         CELL_LENGTH_BIT_NUM +

                                         MOVE_DEST_RANGE_BIT_NUM + 1); 

                                        //移动目标点X坐标上相对当前点的偏移

        unsigned    m_nDestY        :   (REGION_GRID_HEIGHT_BIT_NUM +

                                         CELL_LENGTH_BIT_NUM +

                                         MOVE_DEST_RANGE_BIT_NUM + 1);

                                        //移动目标点Y坐标上相对当前点的偏移

        unsigned    m_nLifePercent  :    LIFE_PERCENT_BIT_NUM;  //生命百分比

        unsigned    m_nManaPercent  :    MANA_PERCENT_BIT_NUM;  //内力百分比

        unsigned    m_nRagePercent  :    RAGE_PERCENT_BIT_NUM;  //怒气百分比

        unsigned    m_MagicState    :    MAGIC_STATE_BIT_NUM;   //要显示的魔法状态

    }m_SyncCharacterList[1];

};

 

流量F = S * N(Packet)

         = S * N(Player) * F(Player) * D(Player) * 9

在一般情况下:

数据包大小S = 17 Byte

       玩家总数N(Player) = 6400

       玩家密度D(Player) = 4 - 6/Region

       玩家激活的Region个数N(AR) = 6400 / 6 * 9 = 9600

       Npc密度D(Npc) = 3/Region

       激活的Npc总数N(Npc) = N(AR) * D(Npc) = 9600 * 3 = 28800

       玩家跨Region的频率F(Player) = 0.5 /

       NpcRegion的频率F(Npc) = 0.1/

 

F = S * (D(Player) + (D(Player) + D(Npc))) * 9 * N(Player) * F(Player) +

       S * D(Player) * 9 * N(Player) * F(Npc)

 = S * D(Player) * 9 * N(Player) * [ (2 + D(Npc) / D(Player)) * F(Player) + F(Npc)]

 


点击这里复制本文地址

声明

本站发布所有广告信息、下载资源,均来自互联网,非本站自制,与本站无关。 如有侵犯您的合法权益请来信告之。我们会在三个工作日内予以清除。


本站中所有资料、资源文件均来自于网络搜索,仅作为技术学习研究之用,请必须在24小时内删除所下载文件,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担。


本站保证站内提供的所有可下载资源(软件等等)都是按“原样”提供,本站未做过任何改动;但本网站不保证本站提供的下载资源的准确性、安全性和完整性;同时本网站也不承担用户因使用这些下载资源对自己和他人造成任何形式的损失或伤害。 


访问本站的用户必须明白,[资源爱好者]对提供下载的软件等不拥有任何权利,其版权归该下载资源的合法拥有者所有。


本站所有资源均不提供相关技术服务,如果源码下载地址失效则请联系站长进行补发。


本站所有资源展现图片仅供参考

资源爱好者 © 本站发布所有资源,均来自互联网,非本站自制,分享目的仅供大家学习与参考,与本站无关。
如有侵犯您的合法权益请来信告之。我们会在三个工作日内予以清除。邮箱:admin?aihao.org
关于我们|广告合作|网站地图|冀ICP备14009590号|冀公网安备 13018302000072号