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


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


        /// <summary>
        
    /// IStandardTimeSynchronizer 时间同步装置,将服务器的时间作为标准时间参考系。
        
    /// zhuweisky 2008.03.05
        
    /// </summary>
        public interface IStandardTimeSynchronizer
        {
            
    /// <summary>
            
    /// SynchronizeSpanInSecs 自动同步服务器时间的时间间隔。
            
    /// </summary>
            int SynchronizeSpanInSecs { get;set; }

            IPinger Pinger { 
    set; }

            
    void Initialize();

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

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

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

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

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

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

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

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

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

            
    #region IStandardTimeSynchronizer 成员

            
    #region SynchronizeSpanInSecs
            
    private int synchronizeSpanInSecs = 10;
            
    public int SynchronizeSpanInSecs
            {
                
    get { return base.DetectSpanInSecs; }
                
    set { base.DetectSpanInSecs = value; }
            } 
            
    #endregion       

            
    #region Pinger
            
    private IPinger pinger;
            
    public IPinger Pinger
            {
                
    set { pinger = value; }
            } 
            
    #endregion

            
    public void Initialize()
            {
                
    base.Start();
            }

            
    protected override bool DoDetect()
            {
                Stopwatch stopwatch 
    = Stopwatch.StartNew();
                DateTime serverTimeStamp;
                
    this.pinger.Ping(out serverTimeStamp);
                
    this.circleSpanInMSecs = stopwatch.ElapsedMilliseconds;

                DateTime pingBackTime 
    = DateTime.Now;

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

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

                
    return true;
            }

            
    public DateTime GetCurrentStandardTime()
            {
                
    return DateTime.Now.AddMilliseconds(-this.timeModifiedInMSecs);
            }

            
    public void Synchronize()
            {
                
    this.DoDetect();
            }

            
    public double ComputeLifeInMSecs(DateTime msgStampTime)
            {
                DateTime estimateServerCurTime 
    = this.GetCurrentStandardTime();
                TimeSpan span 
    = estimateServerCurTime - msgStampTime;

                
    return span.TotalMilliseconds;
            }
            
            
    #endregion
        }

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

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

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



























  • 相关阅读:
    一、Django CBV and Django RestFramework
    Web框架及Django初始化
    HTTP协议
    Mysql之存储引擎
    Django之ORM字段相关
    Django之视图
    Django之初步实现登录功能,APP及ORM
    jQuery
    C#基础:飞行棋游戏
    C#基础练习
  • 原文地址:https://www.cnblogs.com/zhuweisky/p/1093707.html
Copyright © 2020-2023  润新知