`
soboer
  • 浏览: 1303312 次
文章分类
社区版块
存档分类
最新评论

【网络游戏专题】时间同步装置

 
阅读更多

在网络游戏中,有一个最基本的需求是,如果让一个玩家的动作(比如行走)即时地、流畅地在其它的游戏地理位置相邻的玩家的屏幕上显现,如果是在局域网内,这个不是什么大不了的问题,但是如果游戏玩家是分散在Internet上的用户,由于网络的延时的影响,使得其成为项颇有难度的任务。你只要想一下,玩家A发出的消息经过服务器中转到达玩家B时可能已经经过了几秒钟,那么A和B的游戏状态同步就是个问题了。关于解决状态同步问题的方案有一些,比如,预测拉扯技术、服务器验证同步技术、半服务器同步技术等,但是所有这些技术都需要依赖一个更基础的装置--时间同步装置,时间同步装置用于让所有的游戏客户端、游戏服务端都遵守同一个标准的时间,这样,由不同节点发出的消息上的时间戳才有统一的参考标准,这样我们才能比较准确的计算消息从A到B的延时。
我采用这样的方式来实现时间同步装置:
(1)采用服务器(组)的时间为标准时间,这样所有客户端只要与服务器时钟同步即可。
(2)采用类似ping的技术来测量消息从客户端到服务端再回到客户端所花费的时间。
(3)通过一个系数可以调整消息上行速度和下行速度的比例,该比例默认为1,可以根据网络状况进行动态调整。
(4)可以每隔一段指定时间间隔自动同步一次,也支持强制手动调用同步。
这基本上是时间同步装置的核心思想,在设计时,我考虑把它设计得更方便应用,详细讲解如下:
首先,是定义时间同步装置的接口--IStandardTimeSynchronizer。

///<summary>
///IStandardTimeSynchronizer时间同步装置,将服务器的时间作为标准时间参考系。
///zhuweisky2008.03.05
///</summary>
publicinterfaceIStandardTimeSynchronizer
{
///<summary>
///SynchronizeSpanInSecs自动同步服务器时间的时间间隔。
///</summary>
intSynchronizeSpanInSecs{get;set;}

IPingerPinger{
set;}

voidInitialize();

///<summary>
///GetCurrentStandardTime获取经过本地预测的服务器当前标准时间
///</summary>
DateTimeGetCurrentStandardTime();

///<summary>
///Synchronize与服务器进行时间同步,手动强制同步
///</summary>
voidSynchronize();

///<summary>
///ComputeLifeInMSecs计算标记戳时的时刻到现在过了多少ms。(即,消息的当前年龄:))
///注意,消息中的时间戳也是记录的当时(预测)的服务器标准时间。
///</summary>
///<paramname="timeStamp">消息中的时间戳</param>
doubleComputeLifeInMSecs(DateTimemsgStampTime);
}

Initialize方法用于启动自动同步的定时器。IPinger接口用于发送ping消息到服务器,并带回服务器接收到ping消息的时间:

///<summary>
///IPinger用于向服务器发送ping消息,用于计算消息延迟和预测服务器标准时间。
///服务器对该ping消息的应答中必须包含服务器应答时刻的时间戳。
///zhuweisky2008.01.05
///</summary>
publicinterfaceIPinger
{
///<summary>
///Ping当该方法被调用时,客户端立即向服务器发送ping消息然后阻塞,直至收到服务器对该ping消息的应答后该函数才能返回。
///</summary>
voidPing(outDateTimeserverTimeStamp);
}

注意ping方法的注释:Ping方法被调用时,客户端立即向服务器发送ping消息然后阻塞,直至收到服务器对该ping消息的应答后该函数才能返回。为了避免额外的延迟,服务器必须非常迅速地应答Ping消息。关于服务端和客户端之间的高效通信,我使用通信框架ESFramework来解决。
下面我们继续看时间同步装置的完整实现:

publicclassStandardTimeSynchronizer:BaseCycleEngine,IStandardTimeSynchronizer
{
///<summary>
///timeModifiedInMSecs本地时间与服务器时间的差值(ms)。如果本地时间快,则取正值,否则取负值。
///</summary>
privatedoubletimeModifiedInMSecs=0;

///<summary>
///circleSpanInMSecs从发出ping消息到收到服务器应答的总时间(ms)
///</summary>
privatedoublecircleSpanInMSecs=0;

///<summary>
///coefUpDownSpeed消息下行的速度与上行速度的比例
///</summary>
privatedoublecoefDownUpSpeed=1;

#regionIStandardTimeSynchronizer成员

#regionSynchronizeSpanInSecs
privateintsynchronizeSpanInSecs=10;
publicintSynchronizeSpanInSecs
{
get{returnbase.DetectSpanInSecs;}
set{base.DetectSpanInSecs=value;}
}
#endregion

#regionPinger
privateIPingerpinger;
publicIPingerPinger
{
set{pinger=value;}
}
#endregion

publicvoidInitialize()
{
base.Start();
}

protectedoverrideboolDoDetect()
{
Stopwatchstopwatch
=Stopwatch.StartNew();
DateTimeserverTimeStamp;
this.pinger.Ping(outserverTimeStamp);
this.circleSpanInMSecs=stopwatch.ElapsedMilliseconds;

DateTimepingBackTime
=DateTime.Now;

doubledownCostInMSecs=this.circleSpanInMSecs/(coefDownUpSpeed+1);//消息下行时间
DateTimeestimateServerActionTime=pingBackTime-TimeSpan.FromMilliseconds(downCostInMSecs);

TimeSpantimeModifiedSpan
=estimateServerActionTime-serverTimeStamp;
this.timeModifiedInMSecs=timeModifiedSpan.TotalMilliseconds;

returntrue;
}

publicDateTimeGetCurrentStandardTime()
{
returnDateTime.Now.AddMilliseconds(-this.timeModifiedInMSecs);
}

publicvoidSynchronize()
{
this.DoDetect();
}

publicdoubleComputeLifeInMSecs(DateTimemsgStampTime)
{
DateTimeestimateServerCurTime
=this.GetCurrentStandardTime();
TimeSpanspan
=estimateServerCurTime-msgStampTime;

returnspan.TotalMilliseconds;
}

#endregion
}

BaseCycleEngine是一个循环工作的引擎,用于每隔一段时间执行一次DoDetect方法调用。

上面介绍的是时间同步装置的客户端的实现,服务端的实现相当简单,只需要实现ESFramework中一个消息处理器接口并插入到框架即可。

关于这种方案的缺陷是,如果消息上行速度和下行速度差异很大,而速度比例系数coefDownUpSpeed又设置不当的话,那么时间同步的误差就比较大,不知道你是否有更好的办法了?能够提高时间同步的精确度:)



























分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics